2010-06-21 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System.Core / System / TimeZoneInfo.cs
blobce8c22a83a5e7773c134e0ed897855ee864f37c2
1 /*
2 * System.TimeZoneInfo
4 * Author(s)
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.
27 using System;
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;
39 using System.Text;
41 #if LIBC
42 using System.IO;
43 using Mono;
44 #endif
46 using Microsoft.Win32;
48 namespace System
50 #if NET_4_0 || BOOTSTRAP_NET_4_0
51 [TypeForwardedFrom (Consts.AssemblySystemCore_3_5)]
52 #elif MOONLIGHT
53 [TypeForwardedFrom (Consts.AssemblySystem_Core)]
54 #endif
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 {
65 get {
66 if (disableDaylightSavingTime)
67 return String.Empty;
68 return daylightDisplayName;
72 string displayName;
73 public string DisplayName {
74 get { return displayName; }
77 string id;
78 public string Id {
79 get { return id; }
82 static TimeZoneInfo local;
83 public static TimeZoneInfo Local {
84 get {
85 if (local == null) {
86 #if LIBC
87 try {
88 local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
89 } catch {
90 try {
91 local = FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
92 } catch {
93 throw new TimeZoneNotFoundException ();
96 #else
97 throw new TimeZoneNotFoundException ();
98 #endif
100 return local;
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 {
116 get {
117 if (utc == null)
118 utc = CreateCustomTimeZone ("UTC", new TimeSpan (0), "UTC", "UTC");
119 return utc;
122 #if LIBC
123 static string timeZoneDirectory = null;
124 static string TimeZoneDirectory {
125 get {
126 if (timeZoneDirectory == null)
127 timeZoneDirectory = "/usr/share/zoneinfo";
128 return timeZoneDirectory;
130 set {
131 ClearCachedData ();
132 timeZoneDirectory = value;
135 #endif
136 private AdjustmentRule [] adjustmentRules;
138 #if !NET_2_1
139 static RegistryKey timeZoneKey = null;
140 static bool timeZoneKeySet = false;
141 static RegistryKey TimeZoneKey {
142 get {
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",
149 false);
150 timeZoneKeySet = true;
152 return timeZoneKey;
155 #endif
157 public static void ClearCachedData ()
159 local = null;
160 utc = null;
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)
187 return dateTime;
189 DateTime utc = ConvertTimeToUtc (dateTime);
191 if (destinationTimeZone == TimeZoneInfo.Utc)
192 return 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);
234 else
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)
249 return dateTime;
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)
270 return dateTime;
272 if (dateTime.Kind == DateTimeKind.Utc)
273 return dateTime;
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);
280 else {
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)
303 if (other == null)
304 return false;
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
312 if (id == null)
313 throw new ArgumentNullException ("id");
314 #if !NET_2_1
315 if (TimeZoneKey != null)
317 RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
318 if (key == null)
319 throw new TimeZoneNotFoundException ();
320 return FromRegistryKey(id, key);
322 #endif
323 #if LIBC
324 string filepath = Path.Combine (TimeZoneDirectory, id);
325 return FindSystemTimeZoneByFileName (id, filepath);
326 #else
327 throw new NotImplementedException ();
328 #endif
331 #if LIBC
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];
339 int length;
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");
347 try {
348 return ParseTZBuffer (id, buffer, length);
349 } catch (Exception e) {
350 throw new InvalidTimeZoneException (e.Message);
353 #endif
355 #if !NET_2_1
356 private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
358 byte [] reg_tzi = (byte []) key.GetValue ("TZI");
360 if (reg_tzi == null)
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");
376 int year;
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);
387 else
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)
417 return;
419 DateTime start_date;
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);
429 else {
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);
436 DateTime end_date;
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);
446 else {
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));
459 #endif
461 public static TimeZoneInfo FromSerializedString (string source)
463 throw new NotImplementedException ();
466 public AdjustmentRule [] GetAdjustmentRules ()
468 if (disableDaylightSavingTime)
469 return new AdjustmentRule [0];
470 else
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 ();
496 return hash_code;
499 #if NET_4_0
500 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
501 #else
502 public void GetObjectData (SerializationInfo info, StreamingContext context)
503 #endif
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> ();
514 #if !NET_2_1
515 if (TimeZoneKey != null) {
516 foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
517 try {
518 systemTimeZones.Add (FindSystemTimeZoneById (id));
519 } catch {}
522 return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
524 #endif
525 #if LIBC
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) {
528 try {
529 foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
530 try {
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) {
537 throw;
540 } catch {}
542 #else
543 throw new NotImplementedException ("This method is not implemented for this platform");
544 #endif
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)
566 if (other == null)
567 throw new ArgumentNullException ("other");
569 if ((this.adjustmentRules == null) != (other.adjustmentRules == null))
570 return false;
572 if (this.adjustmentRules == null)
573 return true;
575 if (this.BaseUtcOffset != other.BaseUtcOffset)
576 return false;
578 if (this.adjustmentRules.Length != other.adjustmentRules.Length)
579 return false;
581 for (int i = 0; i < adjustmentRules.Length; i++) {
582 if (! (this.adjustmentRules [i]).Equals (other.adjustmentRules [i]))
583 return false;
586 return true;
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)
595 return false;
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)
606 return true;
608 return false;
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)
622 return false;
624 if (!SupportsDaylightSavingTime)
625 return false;
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);
635 if (rule == null)
636 return false;
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)
656 return false;
657 if (dateTime.Kind == DateTimeKind.Local && this != Local)
658 return false;
660 AdjustmentRule rule = GetApplicableRule (dateTime);
661 DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
662 if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
663 return true;
665 return false;
668 #if NET_4_0
669 void IDeserializationCallback.OnDeserialization (object sender)
670 #else
671 public void OnDeserialization (object sender)
672 #endif
674 throw new NotImplementedException ();
677 public string ToSerializedString ()
679 throw new NotImplementedException ();
682 public override string ToString ()
684 return DisplayName;
687 private TimeZoneInfo (string id, TimeSpan baseUtcOffset, string displayName, string standardDisplayName, string daylightDisplayName, TimeZoneInfo.AdjustmentRule [] adjustmentRules, bool disableDaylightSavingTime)
689 if (id == null)
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");
701 #if STRICT
702 if (id.Length > 32)
703 throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
704 #endif
706 if (adjustmentRules != null && adjustmentRules.Length != 0) {
707 AdjustmentRule prev = null;
708 foreach (AdjustmentRule current in adjustmentRules) {
709 if (current == null)
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");
725 prev = current;
729 this.id = id;
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 foreach (AdjustmentRule rule in adjustmentRules) {
750 if (rule.DateStart > date.Date)
751 return null;
752 if (rule.DateEnd < date.Date)
753 continue;
754 return rule;
756 return null;
759 private static DateTime TransitionPoint (TransitionTime transition, int year)
761 if (transition.IsFixedDateRule)
762 return new DateTime (year, transition.Month, transition.Day) + transition.TimeOfDay.TimeOfDay;
764 DayOfWeek first = (new DateTime (year, transition.Month, 1)).DayOfWeek;
765 int day = 1 + (transition.Week - 1) * 7 + (transition.DayOfWeek - first) % 7;
766 if (day > DateTime.DaysInMonth (year, transition.Month))
767 day -= 7;
768 return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
771 #if LIBC
772 private static bool ValidTZFile (byte [] buffer, int length)
774 StringBuilder magic = new StringBuilder ();
776 for (int i = 0; i < 4; i++)
777 magic.Append ((char)buffer [i]);
779 if (magic.ToString () != "TZif")
780 return false;
782 if (length >= BUFFER_SIZE)
783 return false;
785 return true;
788 struct TimeType
790 public readonly int Offset;
791 public readonly bool IsDst;
792 public string Name;
794 public TimeType (int offset, bool is_dst, string abbrev)
796 this.Offset = offset;
797 this.IsDst = is_dst;
798 this.Name = abbrev;
801 public override string ToString ()
803 return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
807 static int SwapInt32 (int i)
809 return (((i >> 24) & 0xff)
810 | ((i >> 8) & 0xff00)
811 | ((i << 8) & 0xff0000)
812 | ((i << 24)));
815 static int ReadBigEndianInt32 (byte [] buffer, int start)
817 int i = BitConverter.ToInt32 (buffer, start);
818 if (!BitConverter.IsLittleEndian)
819 return i;
821 return SwapInt32 (i);
824 private static TimeZoneInfo ParseTZBuffer (string id, byte [] buffer, int length)
826 //Reading the header. 4 bytes for magic, 16 are reserved
827 int ttisgmtcnt = ReadBigEndianInt32 (buffer, 20);
828 int ttisstdcnt = ReadBigEndianInt32 (buffer, 24);
829 int leapcnt = ReadBigEndianInt32 (buffer, 28);
830 int timecnt = ReadBigEndianInt32 (buffer, 32);
831 int typecnt = ReadBigEndianInt32 (buffer, 36);
832 int charcnt = ReadBigEndianInt32 (buffer, 40);
834 if (length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt)
835 throw new InvalidTimeZoneException ();
837 Dictionary<int, string> abbreviations = ParseAbbreviations (buffer, 44 + 4 * timecnt + timecnt + 6 * typecnt, charcnt);
838 Dictionary<int, TimeType> time_types = ParseTimesTypes (buffer, 44 + 4 * timecnt + timecnt, typecnt, abbreviations);
839 List<KeyValuePair<DateTime, TimeType>> transitions = ParseTransitions (buffer, 44, timecnt, time_types);
841 if (time_types.Count == 0)
842 throw new InvalidTimeZoneException ();
844 if (time_types.Count == 1 && ((TimeType)time_types[0]).IsDst)
845 throw new InvalidTimeZoneException ();
847 TimeSpan baseUtcOffset = new TimeSpan (0);
848 TimeSpan dstDelta = new TimeSpan (0);
849 string standardDisplayName = null;
850 string daylightDisplayName = null;
851 bool dst_observed = false;
852 DateTime dst_start = DateTime.MinValue;
853 List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
855 for (int i = 0; i < transitions.Count; i++) {
856 var pair = transitions [i];
857 DateTime ttime = pair.Key;
858 TimeType ttype = pair.Value;
859 if (!ttype.IsDst) {
860 if (standardDisplayName != ttype.Name || baseUtcOffset.TotalSeconds != ttype.Offset) {
861 standardDisplayName = ttype.Name;
862 daylightDisplayName = null;
863 baseUtcOffset = new TimeSpan (0, 0, ttype.Offset);
864 adjustmentRules = new List<AdjustmentRule> ();
865 dst_observed = false;
867 if (dst_observed) {
868 //FIXME: check additional fields for this:
869 //most of the transitions are expressed in GMT
870 dst_start += baseUtcOffset;
871 DateTime dst_end = ttime + baseUtcOffset + dstDelta;
873 //some weird timezone (America/Phoenix) have end dates on Jan 1st
874 if (dst_end.Date == new DateTime (dst_end.Year, 1, 1) && dst_end.Year > dst_start.Year)
875 dst_end -= new TimeSpan (24, 0, 0);
877 DateTime dateStart, dateEnd;
878 if (dst_start.Month < 7)
879 dateStart = new DateTime (dst_start.Year, 1, 1);
880 else
881 dateStart = new DateTime (dst_start.Year, 7, 1);
883 if (dst_end.Month >= 7)
884 dateEnd = new DateTime (dst_end.Year, 12, 31);
885 else
886 dateEnd = new DateTime (dst_end.Year, 6, 30);
889 TransitionTime transition_start = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_start.TimeOfDay, dst_start.Month, dst_start.Day);
890 TransitionTime transition_end = TransitionTime.CreateFixedDateRule (new DateTime (1, 1, 1) + dst_end.TimeOfDay, dst_end.Month, dst_end.Day);
891 if (transition_start != transition_end) //y, that happened in Argentina in 1943-1946
892 adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, dstDelta, transition_start, transition_end));
894 dst_observed = false;
895 } else {
896 if (daylightDisplayName != ttype.Name || dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
897 daylightDisplayName = ttype.Name;
898 dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
900 dst_start = ttime;
901 dst_observed = true;
905 if (adjustmentRules.Count == 0) {
906 TimeType t = (TimeType)time_types [0];
907 if (standardDisplayName == null) {
908 standardDisplayName = t.Name;
909 baseUtcOffset = new TimeSpan (0, 0, t.Offset);
911 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName);
912 } else {
913 return CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
917 static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
919 AdjustmentRule prev = null;
920 foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
921 if (prev != null && prev.DateEnd > current.DateStart) {
922 adjustmentRules.Remove (current);
924 prev = current;
926 return adjustmentRules;
929 static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
931 var abbrevs = new Dictionary<int, string> ();
932 int abbrev_index = 0;
933 var sb = new StringBuilder ();
934 for (int i = 0; i < count; i++) {
935 char c = (char) buffer [index + i];
936 if (c != '\0')
937 sb.Append (c);
938 else {
939 abbrevs.Add (abbrev_index, sb.ToString ());
940 //Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
941 for (int j = 1; j < sb.Length; j++)
942 abbrevs.Add (abbrev_index + j, sb.ToString (j, sb.Length - j));
943 abbrev_index = i + 1;
944 sb = new StringBuilder ();
947 return abbrevs;
950 static Dictionary<int, TimeType> ParseTimesTypes (byte [] buffer, int index, int count, Dictionary<int, string> abbreviations)
952 var types = new Dictionary<int, TimeType> (count);
953 for (int i = 0; i < count; i++) {
954 int offset = ReadBigEndianInt32 (buffer, index + 6 * i);
955 byte is_dst = buffer [index + 6 * i + 4];
956 byte abbrev = buffer [index + 6 * i + 5];
957 types.Add (i, new TimeType (offset, (is_dst != 0), abbreviations [(int)abbrev]));
959 return types;
962 static List<KeyValuePair<DateTime, TimeType>> ParseTransitions (byte [] buffer, int index, int count, Dictionary<int, TimeType> time_types)
964 var list = new List<KeyValuePair<DateTime, TimeType>> (count);
965 for (int i = 0; i < count; i++) {
966 int unixtime = ReadBigEndianInt32 (buffer, index + 4 * i);
967 DateTime ttime = DateTimeFromUnixTime (unixtime);
968 byte ttype = buffer [index + 4 * count + i];
969 list.Add (new KeyValuePair<DateTime, TimeType> (ttime, time_types [(int)ttype]));
971 return list;
974 static DateTime DateTimeFromUnixTime (long unix_time)
976 DateTime date_time = new DateTime (1970, 1, 1);
977 return date_time.AddSeconds (unix_time);
979 #endif
983 #endif