using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
+using System.Runtime.InteropServices;
using System.Text;
using System.Globalization;
-
-#if LIBC || MONODROID
using System.IO;
-using Mono;
-#endif
using Microsoft.Win32;
*/
private List<KeyValuePair<DateTime, TimeType>> transitions;
- static TimeZoneInfo CreateLocal ()
+ private static bool libcNotFound;
+
+ [DllImport ("libc")]
+ private static extern int readlink (string path, byte[] buffer, int buflen);
+
+ private static string readlink (string path)
{
-#if MONODROID
- return AndroidTimeZones.Local;
-#elif MONOTOUCH
- using (Stream stream = GetMonoTouchData (null)) {
- return BuildFromStream ("Local", stream);
+ if (libcNotFound)
+ return null;
+
+ byte[] buf = new byte [512];
+ int ret;
+
+ try {
+ ret = readlink (path, buf, buf.Length);
+ } catch (DllNotFoundException e) {
+ libcNotFound = true;
+ return null;
}
-#else
-#if !NET_2_1
+
+ if (ret == -1) return null;
+ char[] cbuf = new char [512];
+ int chars = System.Text.Encoding.Default.GetChars (buf, 0, ret, cbuf, 0);
+ return new String (cbuf, 0, chars);
+ }
+
+ private static bool TryGetNameFromPath (string path, out string name)
+ {
+ name = null;
+ var linkPath = readlink (path);
+ if (linkPath != null)
+ path = linkPath;
+
+ path = Path.GetFullPath (path);
+
+ if (string.IsNullOrEmpty (TimeZoneDirectory))
+ return false;
+
+ var baseDir = TimeZoneDirectory;
+ if (baseDir [baseDir.Length-1] != Path.DirectorySeparatorChar)
+ baseDir += Path.DirectorySeparatorChar;
+
+ if (!path.StartsWith (baseDir, StringComparison.InvariantCulture))
+ return false;
+
+ name = path.Substring (baseDir.Length);
+ if (name == "localtime")
+ name = "Local";
+
+ return true;
+ }
+
+#if !MOBILE || MOBILE_STATIC
+ static TimeZoneInfo CreateLocal ()
+ {
+#if !MOBILE_STATIC
if (IsWindows && LocalZoneKey != null) {
string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
if (name == null)
}
}
- try {
- return FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
- } catch {
+ var tzFilePaths = new string [] {
+ "/etc/localtime",
+ Path.Combine (TimeZoneDirectory, "localtime")};
+
+ foreach (var tzFilePath in tzFilePaths) {
try {
- return FindSystemTimeZoneByFileName ("Local", Path.Combine (TimeZoneDirectory, "localtime"));
- } catch {
- return null;
+ string tzName = null;
+ if (!TryGetNameFromPath (tzFilePath, out tzName))
+ tzName = "Local";
+ return FindSystemTimeZoneByFileName (tzName, tzFilePath);
+ } catch (TimeZoneNotFoundException) {
+ continue;
+ }
+ }
+
+ return Utc;
+ }
+
+ static TimeZoneInfo FindSystemTimeZoneByIdCore (string id)
+ {
+#if LIBC
+ string filepath = Path.Combine (TimeZoneDirectory, id);
+ return FindSystemTimeZoneByFileName (id, filepath);
+#else
+ throw new NotImplementedException ();
+#endif
+ }
+
+ static void GetSystemTimeZonesCore (List<TimeZoneInfo> systemTimeZones)
+ {
+#if !MOBILE_STATIC
+ if (TimeZoneKey != null) {
+ foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
+ try {
+ systemTimeZones.Add (FindSystemTimeZoneById (id));
+ } catch {}
}
+
+ return;
}
#endif
+
+#if LIBC
+ string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
+ foreach (string continent in continents) {
+ try {
+ foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
+ try {
+ string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
+ systemTimeZones.Add (FindSystemTimeZoneById (id));
+ } catch (ArgumentNullException) {
+ } catch (TimeZoneNotFoundException) {
+ } catch (InvalidTimeZoneException) {
+ } catch (Exception) {
+ throw;
+ }
+ }
+ } catch {}
+ }
+#else
+ throw new NotImplementedException ("This method is not implemented for this platform");
+#endif
}
+#endif
string standardDisplayName;
public string StandardName {
#endif
private AdjustmentRule [] adjustmentRules;
-#if !NET_2_1
+#if !NET_2_1 || MOBILE_STATIC
/// <summary>
/// Determine whether windows of not (taken Stephane Delcroix's code)
/// </summary>
return str.Substring (Istart, Iend-Istart+1);
}
+#if !MOBILE_STATIC
static RegistryKey timeZoneKey;
static RegistryKey TimeZoneKey {
get {
"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
}
}
+#endif
#endif
private static bool TryAddTicks (DateTime date, long ticks, out DateTime result, DateTimeKind kind = DateTimeKind.Unspecified)
// Local requires special logic that already exists in the Local property (bug #326)
if (id == "Local")
return Local;
-#if MONOTOUCH
- using (Stream stream = GetMonoTouchData (id)) {
- return BuildFromStream (id, stream);
- }
-#elif MONODROID
- var timeZoneInfo = AndroidTimeZones.GetTimeZone (id, id);
- if (timeZoneInfo == null)
- throw new TimeZoneNotFoundException ();
- return timeZoneInfo;
-#elif LIBC
- string filepath = Path.Combine (TimeZoneDirectory, id);
- return FindSystemTimeZoneByFileName (id, filepath);
-#else
- throw new NotImplementedException ();
-#endif
+
+ return FindSystemTimeZoneByIdCore (id);
}
#if LIBC
}
}
#endif
-#if LIBC || MONOTOUCH
- const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
-
- private static TimeZoneInfo BuildFromStream (string id, Stream stream)
- {
- byte [] buffer = new byte [BUFFER_SIZE];
- int length = stream.Read (buffer, 0, BUFFER_SIZE);
-
- if (!ValidTZFile (buffer, length))
- throw new InvalidTimeZoneException ("TZ file too big for the buffer");
-
- try {
- return ParseTZBuffer (id, buffer, length);
- } catch (Exception e) {
- throw new InvalidTimeZoneException (e.Message);
- }
- }
-#endif
#if !NET_2_1
private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
public AdjustmentRule [] GetAdjustmentRules ()
{
- if (!supportsDaylightSavingTime)
+ if (!supportsDaylightSavingTime || adjustmentRules == null)
return new AdjustmentRule [0];
else
return (AdjustmentRule []) adjustmentRules.Clone ();
info.AddValue ("SupportsDaylightSavingTime", SupportsDaylightSavingTime);
}
- //FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
- private static List<TimeZoneInfo> systemTimeZones;
+ static ReadOnlyCollection<TimeZoneInfo> systemTimeZones;
+
public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
{
if (systemTimeZones == null) {
- systemTimeZones = new List<TimeZoneInfo> ();
-#if !NET_2_1
- if (TimeZoneKey != null) {
- foreach (string id in TimeZoneKey.GetSubKeyNames ()) {
- try {
- systemTimeZones.Add (FindSystemTimeZoneById (id));
- } catch {}
- }
-
- return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
- }
-#endif
-#if MONODROID
- foreach (string id in AndroidTimeZones.GetAvailableIds ()) {
- var tz = AndroidTimeZones.GetTimeZone (id, id);
- if (tz != null)
- systemTimeZones.Add (tz);
- }
-#elif MONOTOUCH
- if (systemTimeZones.Count == 0) {
- foreach (string name in GetMonoTouchNames ()) {
- using (Stream stream = GetMonoTouchData (name, false)) {
- if (stream == null)
- continue;
- systemTimeZones.Add (BuildFromStream (name, stream));
- }
- }
- }
-#elif LIBC
- string[] continents = new string [] {"Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Brazil", "Canada", "Chile", "Europe", "Indian", "Mexico", "Mideast", "Pacific", "US"};
- foreach (string continent in continents) {
- try {
- foreach (string zonepath in Directory.GetFiles (Path.Combine (TimeZoneDirectory, continent))) {
- try {
- string id = String.Format ("{0}/{1}", continent, Path.GetFileName (zonepath));
- systemTimeZones.Add (FindSystemTimeZoneById (id));
- } catch (ArgumentNullException) {
- } catch (TimeZoneNotFoundException) {
- } catch (InvalidTimeZoneException) {
- } catch (Exception) {
- throw;
- }
- }
- } catch {}
- }
-#else
- throw new NotImplementedException ("This method is not implemented for this platform");
-#endif
+ var tz = new List<TimeZoneInfo> ();
+ GetSystemTimeZonesCore (tz);
+ Interlocked.CompareExchange (ref systemTimeZones, new ReadOnlyCollection<TimeZoneInfo> (tz), null);
}
- return new ReadOnlyCollection<TimeZoneInfo> (systemTimeZones);
+
+ return systemTimeZones;
}
public TimeSpan GetUtcOffset (DateTime dateTime)
throw new NotImplementedException ();
}
+ internal DaylightTime GetDaylightChanges (int year)
+ {
+ DateTime start = DateTime.MinValue, end = DateTime.MinValue;
+ TimeSpan delta = new TimeSpan ();
+
+ if (transitions != null) {
+ end = DateTime.MaxValue;
+ for (var i = transitions.Count - 1; i >= 0; i--) {
+ var pair = transitions [i];
+ DateTime ttime = pair.Key;
+ TimeType ttype = pair.Value;
+
+ if (ttime.Year > year)
+ continue;
+ if (ttime.Year < year)
+ break;
+
+ if (ttype.IsDst) {
+ // DaylightTime.Delta is relative to the current BaseUtcOffset.
+ delta = new TimeSpan (0, 0, ttype.Offset) - BaseUtcOffset;
+ start = ttime;
+ } else {
+ end = ttime;
+ }
+ }
+
+ // DaylightTime.Start is relative to the Standard time.
+ if (start != DateTime.MinValue)
+ start += BaseUtcOffset;
+
+ // DaylightTime.End is relative to the DST time.
+ if (end != DateTime.MinValue)
+ end += BaseUtcOffset + delta;
+ } else {
+ AdjustmentRule first = null, last = null;
+
+ foreach (var rule in GetAdjustmentRules ()) {
+ if (rule.DateStart.Year != year && rule.DateEnd.Year != year)
+ continue;
+ if (rule.DateStart.Year == year)
+ first = rule;
+ if (rule.DateEnd.Year == year)
+ last = rule;
+ }
+
+ if (first == null || last == null)
+ return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
+
+ start = TransitionPoint (first.DaylightTransitionStart, year);
+ end = TransitionPoint (last.DaylightTransitionEnd, year);
+ delta = first.DaylightDelta;
+ }
+
+ if (start == DateTime.MinValue || end == DateTime.MinValue)
+ return new DaylightTime (new DateTime (), new DateTime (), new TimeSpan ());
+
+ return new DaylightTime (start, end, delta);
+ }
+
public bool IsInvalidTime (DateTime dateTime)
{
if (dateTime.Kind == DateTimeKind.Utc)
return adjustmentRules;
}
-#if LIBC || MONODROID
+#if LIBC || MONOTOUCH
+ const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
+
+ private static TimeZoneInfo BuildFromStream (string id, Stream stream)
+ {
+ byte [] buffer = new byte [BUFFER_SIZE];
+ int length = stream.Read (buffer, 0, BUFFER_SIZE);
+
+ if (!ValidTZFile (buffer, length))
+ throw new InvalidTimeZoneException ("TZ file too big for the buffer");
+
+ try {
+ return ParseTZBuffer (id, buffer, length);
+ } catch (Exception e) {
+ throw new InvalidTimeZoneException (e.Message);
+ }
+ }
+
private static bool ValidTZFile (byte [] buffer, int length)
{
StringBuilder magic = new StringBuilder ();
} else {
if (daylightDisplayName != ttype.Name)
daylightDisplayName = ttype.Name;
- if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds)
- dstDelta = new TimeSpan(0, 0, ttype.Offset) - baseUtcOffset;
+ if (dstDelta.TotalSeconds != ttype.Offset - baseUtcOffset.TotalSeconds) {
+ // Round to nearest minute, since it's not possible to create an adjustment rule
+ // with sub-minute precision ("The TimeSpan parameter cannot be specified more precisely than whole minutes.")
+ // This happens for instance with Europe/Dublin, which had an offset of 34 minutes and 39 seconds in 1916.
+ dstDelta = new TimeSpan (0, 0, ttype.Offset) - baseUtcOffset;
+ if (dstDelta.Ticks % TimeSpan.TicksPerMinute != 0)
+ dstDelta = TimeSpan.FromMinutes ((long) (dstDelta.TotalMinutes + 0.5f));
+ }
dst_start = ttime;
dst_observed = true;
tz = CreateCustomTimeZone (id, baseUtcOffset, id, standardDisplayName, daylightDisplayName, ValidateRules (adjustmentRules).ToArray ());
}
- if (storeTransition)
+ if (storeTransition && transitions.Count > 0) {
tz.transitions = transitions;
+ tz.supportsDaylightSavingTime = true;
+ }
return tz;
}