* Author(s)
* Stephane Delcroix <stephane@delcroix.org>
*
+ * Copyright 2011 Xamarin Inc.
+ *
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
using System;
using System.Runtime.CompilerServices;
-#if !INSIDE_CORLIB && (NET_4_0 || BOOTSTRAP_NET_4_0)
+#if !INSIDE_CORLIB && (NET_4_0 || MOONLIGHT || MOBILE)
[assembly:TypeForwardedTo (typeof(TimeZoneInfo))]
-#elif NET_3_5 || (NET_2_1 && !INSIDE_CORLIB)
+#elif (INSIDE_CORLIB && (NET_4_0 || MOONLIGHT || MOBILE)) || (!INSIDE_CORLIB && (NET_3_5 && !NET_4_0 && !MOBILE))
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.Text;
-#if LIBC
+#if LIBC || MONODROID
using System.IO;
using Mono;
#endif
+using Microsoft.Win32;
+
namespace System
{
-#if NET_4_0 || BOOTSTRAP_NET_4_0
+#if NET_4_0
[TypeForwardedFrom (Consts.AssemblySystemCore_3_5)]
-#endif
+#elif MOONLIGHT || MOBILE
+ [TypeForwardedFrom (Consts.AssemblySystem_Core)]
+#endif
[SerializableAttribute]
public sealed partial class TimeZoneInfo : IEquatable<TimeZoneInfo>, ISerializable, IDeserializationCallback
{
string daylightDisplayName;
public string DaylightName {
get {
- if (disableDaylightSavingTime)
- return String.Empty;
- return daylightDisplayName;
+ return supportsDaylightSavingTime
+ ? daylightDisplayName
+ : string.Empty;
}
}
public static TimeZoneInfo Local {
get {
if (local == null) {
-#if LIBC
+#if MONODROID
+ local = ZoneInfoDB.Default;
+#elif MONOTOUCH
+ using (Stream stream = GetMonoTouchDefault ()) {
+ local = BuildFromStream ("Local", stream);
+ }
+#elif LIBC
try {
local = FindSystemTimeZoneByFileName ("Local", "/etc/localtime");
} catch {
}
}
#else
- throw new TimeZoneNotFoundException ();
+ if (IsWindows && LocalZoneKey != null) {
+ string name = (string)LocalZoneKey.GetValue ("TimeZoneKeyName");
+ name = TrimSpecial (name);
+ if (name != null)
+ local = TimeZoneInfo.FindSystemTimeZoneById (name);
+ }
+
+ if (local == null)
+ throw new TimeZoneNotFoundException ();
#endif
}
return local;
get { return standardDisplayName; }
}
- bool disableDaylightSavingTime;
+ bool supportsDaylightSavingTime;
public bool SupportsDaylightSavingTime {
- get { return !disableDaylightSavingTime; }
+ get { return supportsDaylightSavingTime; }
}
static TimeZoneInfo utc;
}
}
#if LIBC
- static string timeZoneDirectory = null;
+ static string timeZoneDirectory;
static string TimeZoneDirectory {
get {
if (timeZoneDirectory == null)
#endif
private AdjustmentRule [] adjustmentRules;
+#if !NET_2_1
+ /// <summary>
+ /// Determine whether windows of not (taken Stephane Delcroix's code)
+ /// </summary>
+ private static bool IsWindows
+ {
+ get {
+ int platform = (int) Environment.OSVersion.Platform;
+ return ((platform != 4) && (platform != 6) && (platform != 128));
+ }
+ }
+
+ /// <summary>
+ /// Needed to trim misc garbage in MS registry keys
+ /// </summary>
+ private static string TrimSpecial (string str)
+ {
+ var Istart = 0;
+ while (Istart < str.Length && !char.IsLetterOrDigit(str[Istart])) Istart++;
+ var Iend = str.Length - 1;
+ while (Iend > Istart && !char.IsLetterOrDigit(str[Iend])) Iend--;
+
+ return str.Substring (Istart, Iend-Istart+1);
+ }
+
+ static RegistryKey timeZoneKey;
+ static RegistryKey TimeZoneKey {
+ get {
+ if (timeZoneKey != null)
+ return timeZoneKey;
+ if (!IsWindows)
+ return null;
+
+ return timeZoneKey = Registry.LocalMachine.OpenSubKey (
+ "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
+ false);
+ }
+ }
+
+ static RegistryKey localZoneKey;
+ static RegistryKey LocalZoneKey {
+ get {
+ if (localZoneKey != null)
+ return localZoneKey;
+
+ if (!IsWindows)
+ return null;
+
+ return localZoneKey = Registry.LocalMachine.OpenSubKey (
+ "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", false);
+ }
+ }
+#endif
+
public static void ClearCachedData ()
{
local = null;
public static DateTime ConvertTime (DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone)
{
if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
- throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
+ throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
- throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
+ throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
if (sourceTimeZone.IsInvalidTime (dateTime))
throw new ArgumentException ("dateTime parameter is an invalid time");
}
- public static DateTimeOffset ConvertTime (DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
+ public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone)
{
- throw new NotImplementedException ();
+ if (destinationTimeZone == null)
+ throw new ArgumentNullException("destinationTimeZone");
+
+ var utcDateTime = dateTimeOffset.UtcDateTime;
+ AdjustmentRule rule = GetApplicableRule (utcDateTime);
+
+ if (rule != null && destinationTimeZone.IsDaylightSavingTime(utcDateTime)) {
+ var offset = destinationTimeZone.BaseUtcOffset + rule.DaylightDelta;
+ return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + offset, offset);
+ }
+ else {
+ return new DateTimeOffset(DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified) + destinationTimeZone.BaseUtcOffset, destinationTimeZone.BaseUtcOffset);
+ }
}
public static DateTime ConvertTimeBySystemTimeZoneId (DateTime dateTime, string destinationTimeZoneId)
AdjustmentRule rule = GetApplicableRule (dateTime);
- if (IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
+ if (rule != null && IsDaylightSavingTime (DateTime.SpecifyKind (dateTime, DateTimeKind.Utc)))
return DateTime.SpecifyKind (dateTime + BaseUtcOffset + rule.DaylightDelta , DateTimeKind.Unspecified);
else
return DateTime.SpecifyKind (dateTime + BaseUtcOffset, DateTimeKind.Unspecified);
throw new ArgumentNullException ("sourceTimeZone");
if (dateTime.Kind == DateTimeKind.Utc && sourceTimeZone != TimeZoneInfo.Utc)
- throw new ArgumentException ("Kind propery of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
+ throw new ArgumentException ("Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc");
if (dateTime.Kind == DateTimeKind.Local && sourceTimeZone != TimeZoneInfo.Local)
- throw new ArgumentException ("Kind propery of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
+ throw new ArgumentException ("Kind property of dateTime is Local but the sourceTimeZone does not equal TimeZoneInfo.Local");
if (sourceTimeZone.IsInvalidTime (dateTime))
throw new ArgumentException ("dateTime parameter is an invalid time");
return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
else {
AdjustmentRule rule = sourceTimeZone.GetApplicableRule (dateTime);
- return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset - rule.DaylightDelta, DateTimeKind.Utc);
+ if (rule != null)
+ return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset - rule.DaylightDelta, DateTimeKind.Utc);
+ else
+ return DateTime.SpecifyKind (dateTime - sourceTimeZone.BaseUtcOffset, DateTimeKind.Utc);
}
}
return new TimeZoneInfo (id, baseUtcOffset, displayName, standardDisplayName, daylightDisplayName, adjustmentRules, disableDaylightSavingTime);
}
+#if NET_4_5
+ public override bool Equals (object obj)
+ {
+ return Equals (obj as TimeZoneInfo);
+ }
+#endif
+
public bool Equals (TimeZoneInfo other)
{
if (other == null)
//FIXME: this method should check for cached values in systemTimeZones
if (id == null)
throw new ArgumentNullException ("id");
-#if LIBC
+#if !NET_2_1
+ if (TimeZoneKey != null)
+ {
+ RegistryKey key = TimeZoneKey.OpenSubKey (id, false);
+ if (key == null)
+ throw new TimeZoneNotFoundException ();
+ return FromRegistryKey(id, key);
+ }
+#endif
+#if MONODROID
+ var timeZoneInfo = ZoneInfoDB.GetTimeZone (id);
+ if (timeZoneInfo == null)
+ throw new TimeZoneNotFoundException ();
+ return timeZoneInfo;
+#else
+ // 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 LIBC
string filepath = Path.Combine (TimeZoneDirectory, id);
return FindSystemTimeZoneByFileName (id, filepath);
#else
throw new NotImplementedException ();
+#endif
#endif
}
#if LIBC
- const int BUFFER_SIZE = 16384; //Big enough for any tz file (on Oct 2008, all tz files are under 10k)
private static TimeZoneInfo FindSystemTimeZoneByFileName (string id, string filepath)
{
if (!File.Exists (filepath))
throw new TimeZoneNotFoundException ();
- byte [] buffer = new byte [BUFFER_SIZE];
- int length;
using (FileStream stream = File.OpenRead (filepath)) {
- length = stream.Read (buffer, 0, BUFFER_SIZE);
+ return BuildFromStream (id, stream);
}
-
+ }
+#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");
}
#endif
+#if !NET_2_1
+ private static TimeZoneInfo FromRegistryKey (string id, RegistryKey key)
+ {
+ byte [] reg_tzi = (byte []) key.GetValue ("TZI");
+
+ if (reg_tzi == null)
+ throw new InvalidTimeZoneException ();
+
+ int bias = BitConverter.ToInt32 (reg_tzi, 0);
+ TimeSpan baseUtcOffset = new TimeSpan (0, -bias, 0);
+
+ string display_name = (string) key.GetValue ("Display");
+ string standard_name = (string) key.GetValue ("Std");
+ string daylight_name = (string) key.GetValue ("Dlt");
+
+ List<AdjustmentRule> adjustmentRules = new List<AdjustmentRule> ();
+
+ RegistryKey dst_key = key.OpenSubKey ("Dynamic DST", false);
+ if (dst_key != null) {
+ int first_year = (int) dst_key.GetValue ("FirstEntry");
+ int last_year = (int) dst_key.GetValue ("LastEntry");
+ int year;
+
+ for (year=first_year; year<=last_year; year++) {
+ byte [] dst_tzi = (byte []) dst_key.GetValue (year.ToString ());
+ if (dst_tzi != null) {
+ int start_year = year == first_year ? 1 : year;
+ int end_year = year == last_year ? 9999 : year;
+ ParseRegTzi(adjustmentRules, start_year, end_year, dst_tzi);
+ }
+ }
+ }
+ else
+ ParseRegTzi(adjustmentRules, 1, 9999, reg_tzi);
+
+ return CreateCustomTimeZone (id, baseUtcOffset, display_name, standard_name, daylight_name, ValidateRules (adjustmentRules).ToArray ());
+ }
+
+ private static void ParseRegTzi (List<AdjustmentRule> adjustmentRules, int start_year, int end_year, byte [] buffer)
+ {
+ //int standard_bias = BitConverter.ToInt32 (buffer, 4); /* not sure how to handle this */
+ int daylight_bias = BitConverter.ToInt32 (buffer, 8);
+
+ int standard_year = BitConverter.ToInt16 (buffer, 12);
+ int standard_month = BitConverter.ToInt16 (buffer, 14);
+ int standard_dayofweek = BitConverter.ToInt16 (buffer, 16);
+ int standard_day = BitConverter.ToInt16 (buffer, 18);
+ int standard_hour = BitConverter.ToInt16 (buffer, 20);
+ int standard_minute = BitConverter.ToInt16 (buffer, 22);
+ int standard_second = BitConverter.ToInt16 (buffer, 24);
+ int standard_millisecond = BitConverter.ToInt16 (buffer, 26);
+
+ int daylight_year = BitConverter.ToInt16 (buffer, 28);
+ int daylight_month = BitConverter.ToInt16 (buffer, 30);
+ int daylight_dayofweek = BitConverter.ToInt16 (buffer, 32);
+ int daylight_day = BitConverter.ToInt16 (buffer, 34);
+ int daylight_hour = BitConverter.ToInt16 (buffer, 36);
+ int daylight_minute = BitConverter.ToInt16 (buffer, 38);
+ int daylight_second = BitConverter.ToInt16 (buffer, 40);
+ int daylight_millisecond = BitConverter.ToInt16 (buffer, 42);
+
+ if (standard_month == 0 || daylight_month == 0)
+ return;
+
+ DateTime start_date;
+ DateTime start_timeofday = new DateTime (1, 1, 1, daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
+ TransitionTime start_transition_time;
+
+ if (daylight_year == 0) {
+ start_date = new DateTime (start_year, 1, 1);
+ start_transition_time = TransitionTime.CreateFloatingDateRule (
+ start_timeofday, daylight_month, daylight_day,
+ (DayOfWeek) daylight_dayofweek);
+ }
+ else {
+ start_date = new DateTime (daylight_year, daylight_month, daylight_day,
+ daylight_hour, daylight_minute, daylight_second, daylight_millisecond);
+ start_transition_time = TransitionTime.CreateFixedDateRule (
+ start_timeofday, daylight_month, daylight_day);
+ }
+
+ DateTime end_date;
+ DateTime end_timeofday = new DateTime (1, 1, 1, standard_hour, standard_minute, standard_second, standard_millisecond);
+ TransitionTime end_transition_time;
+
+ if (standard_year == 0) {
+ end_date = new DateTime (end_year, 12, 31);
+ end_transition_time = TransitionTime.CreateFloatingDateRule (
+ end_timeofday, standard_month, standard_day,
+ (DayOfWeek) standard_dayofweek);
+ }
+ else {
+ end_date = new DateTime (standard_year, standard_month, standard_day,
+ standard_hour, standard_minute, standard_second, standard_millisecond);
+ end_transition_time = TransitionTime.CreateFixedDateRule (
+ end_timeofday, standard_month, standard_day);
+ }
+
+ TimeSpan daylight_delta = new TimeSpan(0, -daylight_bias, 0);
+
+ adjustmentRules.Add (AdjustmentRule.CreateAdjustmentRule (
+ start_date, end_date, daylight_delta,
+ start_transition_time, end_transition_time));
+ }
+#endif
+
public static TimeZoneInfo FromSerializedString (string source)
{
throw new NotImplementedException ();
public AdjustmentRule [] GetAdjustmentRules ()
{
- if (disableDaylightSavingTime)
+ if (!supportsDaylightSavingTime)
return new AdjustmentRule [0];
else
return (AdjustmentRule []) adjustmentRules.Clone ();
throw new ArgumentException ("dateTime is not an ambiguous time");
AdjustmentRule rule = GetApplicableRule (dateTime);
- return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
+ if (rule != null)
+ return new TimeSpan[] {baseUtcOffset, baseUtcOffset + rule.DaylightDelta};
+ else
+ return new TimeSpan[] {baseUtcOffset, baseUtcOffset};
}
public TimeSpan [] GetAmbiguousTimeOffsets (DateTimeOffset dateTimeOffset)
}
//FIXME: change this to a generic Dictionary and allow caching for FindSystemTimeZoneById
- private static List<TimeZoneInfo> systemTimeZones = null;
+ private static List<TimeZoneInfo> systemTimeZones;
public static ReadOnlyCollection<TimeZoneInfo> GetSystemTimeZones ()
{
if (systemTimeZones == null) {
systemTimeZones = new List<TimeZoneInfo> ();
-#if LIBC
+#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 ZoneInfoDB.GetAvailableIds ()) {
+ var tz = ZoneInfoDB.GetTimeZone (id);
+ if (tz != null)
+ systemTimeZones.Add (tz);
+ }
+#elif MONOTOUCH
+ if (systemTimeZones.Count == 0) {
+ foreach (string name in GetMonoTouchNames ()) {
+ using (Stream stream = GetMonoTouchData (name)) {
+ 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 {
{
if (IsDaylightSavingTime (dateTime)) {
AdjustmentRule rule = GetApplicableRule (dateTime);
- return BaseUtcOffset + rule.DaylightDelta;
+ if (rule != null)
+ return BaseUtcOffset + rule.DaylightDelta;
}
return BaseUtcOffset;
dateTime = ConvertTime (dateTime, TimeZoneInfo.Local, this);
AdjustmentRule rule = GetApplicableRule (dateTime);
- DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
- if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
- return true;
+ if (rule != null) {
+ DateTime tpoint = TransitionPoint (rule.DaylightTransitionEnd, dateTime.Year);
+ if (dateTime > tpoint - rule.DaylightDelta && dateTime <= tpoint)
+ return true;
+ }
return false;
}
return false;
AdjustmentRule rule = GetApplicableRule (dateTime);
- DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
- if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
- return true;
+ if (rule != null) {
+ DateTime tpoint = TransitionPoint (rule.DaylightTransitionStart, dateTime.Year);
+ if (dateTime >= tpoint && dateTime < tpoint + rule.DaylightDelta)
+ return true;
+ }
return false;
}
throw new ArgumentException ("id parameter shouldn't be longer than 32 characters");
#endif
+ bool supportsDaylightSavingTime = !disableDaylightSavingTime;
+
if (adjustmentRules != null && adjustmentRules.Length != 0) {
AdjustmentRule prev = null;
foreach (AdjustmentRule current in adjustmentRules) {
prev = current;
}
+ } else {
+ supportsDaylightSavingTime = false;
}
this.id = id;
this.displayName = displayName ?? id;
this.standardDisplayName = standardDisplayName ?? id;
this.daylightDisplayName = daylightDisplayName;
- this.disableDaylightSavingTime = disableDaylightSavingTime;
+ this.supportsDaylightSavingTime = supportsDaylightSavingTime;
this.adjustmentRules = adjustmentRules;
}
if (dateTime.Kind == DateTimeKind.Utc && this != TimeZoneInfo.Utc)
date = date + BaseUtcOffset;
- foreach (AdjustmentRule rule in adjustmentRules) {
- if (rule.DateStart > date.Date)
- return null;
- if (rule.DateEnd < date.Date)
- continue;
- return rule;
+ if (adjustmentRules != null) {
+ foreach (AdjustmentRule rule in adjustmentRules) {
+ if (rule.DateStart > date.Date)
+ return null;
+ if (rule.DateEnd < date.Date)
+ continue;
+ return rule;
+ }
}
return null;
}
return new DateTime (year, transition.Month, day) + transition.TimeOfDay.TimeOfDay;
}
-#if LIBC
+ static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
+ {
+ AdjustmentRule prev = null;
+ foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
+ if (prev != null && prev.DateEnd > current.DateStart) {
+ adjustmentRules.Remove (current);
+ }
+ prev = current;
+ }
+ return adjustmentRules;
+ }
+
+#if LIBC || MONODROID
private static bool ValidTZFile (byte [] buffer, int length)
{
StringBuilder magic = new StringBuilder ();
return true;
}
- struct TimeType
- {
- public readonly int Offset;
- public readonly bool IsDst;
- public string Name;
-
- public TimeType (int offset, bool is_dst, string abbrev)
- {
- this.Offset = offset;
- this.IsDst = is_dst;
- this.Name = abbrev;
- }
-
- public override string ToString ()
- {
- return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
- }
- }
-
static int SwapInt32 (int i)
{
return (((i >> 24) & 0xff)
}
}
- static List<AdjustmentRule> ValidateRules (List<AdjustmentRule> adjustmentRules)
- {
- AdjustmentRule prev = null;
- foreach (AdjustmentRule current in adjustmentRules.ToArray ()) {
- if (prev != null && prev.DateEnd > current.DateStart) {
- adjustmentRules.Remove (current);
- }
- prev = current;
- }
- return adjustmentRules;
- }
-
static Dictionary<int, string> ParseAbbreviations (byte [] buffer, int index, int count)
{
var abbrevs = new Dictionary<int, string> ();
DateTime date_time = new DateTime (1970, 1, 1);
return date_time.AddSeconds (unix_time);
}
+ }
+
+ struct TimeType {
+ public readonly int Offset;
+ public readonly bool IsDst;
+ public string Name;
+
+ public TimeType (int offset, bool is_dst, string abbrev)
+ {
+ this.Offset = offset;
+ this.IsDst = is_dst;
+ this.Name = abbrev;
+ }
+
+ public override string ToString ()
+ {
+ return "offset: " + Offset + "s, is_dst: " + IsDst + ", zone name: " + Name;
+ }
+#else
+ }
#endif
}
}