2014-08-13 10:39:27 +01:00
/ *
* System . TimeZoneInfo
*
* 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
* without limitation the rights to use , copy , modify , merge , publish ,
* distribute , sublicense , and / or sell copies of the Software , and to
* permit persons to whom the Software is furnished to do so , subject to
* the following conditions :
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND ,
* EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT . IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN ACTION
* OF CONTRACT , TORT OR OTHERWISE , ARISING FROM , OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE .
* /
using System ;
using System.Runtime.CompilerServices ;
using System.Threading ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Runtime.Serialization ;
2016-02-22 11:00:01 -05:00
using System.Runtime.InteropServices ;
2014-08-13 10:39:27 +01:00
using System.Text ;
2015-04-07 09:35:12 +01:00
using System.Globalization ;
2014-08-13 10:39:27 +01:00
using System.IO ;
using Microsoft.Win32 ;
namespace System
{
2015-08-26 07:17:56 -04:00
partial class TimeZoneInfo
2014-08-13 10:39:27 +01:00
{
TimeSpan baseUtcOffset ;
public TimeSpan BaseUtcOffset {
get { return baseUtcOffset ; }
}
string daylightDisplayName ;
public string DaylightName {
get {
return supportsDaylightSavingTime
? daylightDisplayName
: string . Empty ;
}
}
string displayName ;
public string DisplayName {
get { return displayName ; }
}
string id ;
public string Id {
get { return id ; }
}
static TimeZoneInfo local ;
public static TimeZoneInfo Local {
get {
var l = local ;
if ( l = = null ) {
l = CreateLocal ( ) ;
if ( l = = null )
throw new TimeZoneNotFoundException ( ) ;
if ( Interlocked . CompareExchange ( ref local , l , null ) ! = null )
l = local ;
}
return l ;
}
}
2015-01-13 10:44:36 +00:00
/ *
TimeZone transitions are stored when there is a change on the base offset .
* /
private List < KeyValuePair < DateTime , TimeType > > transitions ;
2016-08-03 10:59:49 +00:00
private static bool readlinkNotFound ;
2016-02-22 11:00:01 -05:00
[DllImport ("libc")]
private static extern int readlink ( string path , byte [ ] buffer , int buflen ) ;
private static string readlink ( string path )
{
2016-08-03 10:59:49 +00:00
if ( readlinkNotFound )
2016-02-22 11:00:01 -05:00
return null ;
byte [ ] buf = new byte [ 512 ] ;
int ret ;
try {
ret = readlink ( path , buf , buf . Length ) ;
} catch ( DllNotFoundException e ) {
2016-08-03 10:59:49 +00:00
readlinkNotFound = true ;
return null ;
} catch ( EntryPointNotFoundException e ) {
readlinkNotFound = true ;
2016-02-22 11:00:01 -05:00
return null ;
}
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 ) ;
2016-08-03 10:59:49 +00:00
if ( linkPath ! = null ) {
if ( Path . IsPathRooted ( linkPath ) )
path = linkPath ;
else
path = Path . Combine ( Path . GetDirectoryName ( path ) , linkPath ) ;
}
2016-02-22 11:00:01 -05:00
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 ;
}
2015-08-26 07:17:56 -04:00
#if ! MOBILE | | MOBILE_STATIC
2014-08-13 10:39:27 +01:00
static TimeZoneInfo CreateLocal ( )
{
2015-08-26 07:17:56 -04:00
#if ! MOBILE_STATIC
2015-04-07 09:35:12 +01:00
if ( IsWindows & & LocalZoneKey ! = null ) {
string name = ( string ) LocalZoneKey . GetValue ( "TimeZoneKeyName" ) ;
2015-08-26 07:17:56 -04:00
if ( name = = null )
name = ( string ) LocalZoneKey . GetValue ( "StandardName" ) ; // windows xp
2015-04-07 09:35:12 +01:00
name = TrimSpecial ( name ) ;
if ( name ! = null )
return TimeZoneInfo . FindSystemTimeZoneById ( name ) ;
}
#endif
2015-01-13 10:44:36 +00:00
var tz = Environment . GetEnvironmentVariable ( "TZ" ) ;
if ( tz ! = null ) {
if ( tz = = String . Empty )
return Utc ;
try {
return FindSystemTimeZoneByFileName ( tz , Path . Combine ( TimeZoneDirectory , tz ) ) ;
} catch {
return Utc ;
}
}
2016-02-22 11:00:01 -05:00
var tzFilePaths = new string [ ] {
"/etc/localtime" ,
Path . Combine ( TimeZoneDirectory , "localtime" ) } ;
foreach ( var tzFilePath in tzFilePaths ) {
2014-08-13 10:39:27 +01:00
try {
2016-02-22 11:00:01 -05:00
string tzName = null ;
if ( ! TryGetNameFromPath ( tzFilePath , out tzName ) )
tzName = "Local" ;
return FindSystemTimeZoneByFileName ( tzName , tzFilePath ) ;
2015-08-26 07:17:56 -04:00
} catch ( TimeZoneNotFoundException ) {
2016-02-22 11:00:01 -05:00
continue ;
2014-08-13 10:39:27 +01:00
}
}
2016-02-22 11:00:01 -05:00
return Utc ;
2015-08-26 07:17:56 -04:00
}
static TimeZoneInfo FindSystemTimeZoneByIdCore ( string id )
{
#if LIBC
string filepath = Path . Combine ( TimeZoneDirectory , id ) ;
return FindSystemTimeZoneByFileName ( id , filepath ) ;
#else
throw new NotImplementedException ( ) ;
#endif
}
2016-02-22 11:00:01 -05:00
static void GetSystemTimeZonesCore ( List < TimeZoneInfo > systemTimeZones )
2015-08-26 07:17:56 -04:00
{
#if ! MOBILE_STATIC
if ( TimeZoneKey ! = null ) {
foreach ( string id in TimeZoneKey . GetSubKeyNames ( ) ) {
try {
systemTimeZones . Add ( FindSystemTimeZoneById ( id ) ) ;
} catch { }
}
return ;
}
#endif
#if LIBC
2016-02-22 11:00:01 -05:00
string [ ] continents = new string [ ] { "Africa" , "America" , "Antarctica" , "Arctic" , "Asia" , "Atlantic" , "Australia" , "Brazil" , "Canada" , "Chile" , "Europe" , "Indian" , "Mexico" , "Mideast" , "Pacific" , "US" } ;
2015-08-26 07:17:56 -04:00
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" ) ;
2014-08-13 10:39:27 +01:00
#endif
}
2015-08-26 07:17:56 -04:00
#endif
2014-08-13 10:39:27 +01:00
string standardDisplayName ;
public string StandardName {
get { return standardDisplayName ; }
}
bool supportsDaylightSavingTime ;
public bool SupportsDaylightSavingTime {
get { return supportsDaylightSavingTime ; }
}
static TimeZoneInfo utc ;
public static TimeZoneInfo Utc {
get {
if ( utc = = null )
utc = CreateCustomTimeZone ( "UTC" , new TimeSpan ( 0 ) , "UTC" , "UTC" ) ;
return utc ;
}
}
#if LIBC
static string timeZoneDirectory ;
static string TimeZoneDirectory {
get {
if ( timeZoneDirectory = = null )
timeZoneDirectory = "/usr/share/zoneinfo" ;
return timeZoneDirectory ;
}
set {
ClearCachedData ( ) ;
timeZoneDirectory = value ;
}
}
#endif
private AdjustmentRule [ ] adjustmentRules ;
2015-08-26 07:17:56 -04:00
#if ! NET_2_1 | | MOBILE_STATIC
2014-08-13 10:39:27 +01:00
/// <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 )
{
2015-08-26 07:17:56 -04:00
if ( str = = null )
return str ;
2014-08-13 10:39:27 +01:00
var Istart = 0 ;
while ( Istart < str . Length & & ! char . IsLetterOrDigit ( str [ Istart ] ) ) Istart + + ;
var Iend = str . Length - 1 ;
2016-03-16 12:38:19 -04:00
while ( Iend > Istart & & ! char . IsLetterOrDigit ( str [ Iend ] ) & & str [ Iend ] ! = ')' ) // zone name can include parentheses like "Central Standard Time (Mexico)"
Iend - - ;
2014-08-13 10:39:27 +01:00
return str . Substring ( Istart , Iend - Istart + 1 ) ;
}
2015-08-26 07:17:56 -04:00
#if ! MOBILE_STATIC
2014-08-13 10:39:27 +01:00
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 ) ;
}
}
2015-08-26 07:17:56 -04:00
#endif
2014-08-13 10:39:27 +01:00
#endif
2015-04-26 19:10:23 +01:00
private static bool TryAddTicks ( DateTime date , long ticks , out DateTime result , DateTimeKind kind = DateTimeKind . Unspecified )
{
var resultTicks = date . Ticks + ticks ;
2016-02-22 11:00:01 -05:00
if ( resultTicks < DateTime . MinValue . Ticks ) {
result = DateTime . SpecifyKind ( DateTime . MinValue , kind ) ;
return false ;
}
if ( resultTicks > DateTime . MaxValue . Ticks ) {
result = DateTime . SpecifyKind ( DateTime . MaxValue , kind ) ;
2015-04-26 19:10:23 +01:00
return false ;
}
result = new DateTime ( resultTicks , kind ) ;
return true ;
}
2014-08-13 10:39:27 +01:00
public static void ClearCachedData ( )
{
local = null ;
utc = null ;
systemTimeZones = null ;
}
public static DateTime ConvertTime ( DateTime dateTime , TimeZoneInfo destinationTimeZone )
{
return ConvertTime ( dateTime , dateTime . Kind = = DateTimeKind . Utc ? TimeZoneInfo . Utc : TimeZoneInfo . Local , destinationTimeZone ) ;
}
public static DateTime ConvertTime ( DateTime dateTime , TimeZoneInfo sourceTimeZone , TimeZoneInfo destinationTimeZone )
{
if ( sourceTimeZone = = null )
throw new ArgumentNullException ( "sourceTimeZone" ) ;
if ( destinationTimeZone = = null )
throw new ArgumentNullException ( "destinationTimeZone" ) ;
if ( dateTime . Kind = = DateTimeKind . Local & & sourceTimeZone ! = 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 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" ) ;
if ( dateTime . Kind = = DateTimeKind . Local & & sourceTimeZone = = TimeZoneInfo . Local & & destinationTimeZone = = TimeZoneInfo . Local )
return dateTime ;
2015-04-07 09:35:12 +01:00
DateTime utc = ConvertTimeToUtc ( dateTime , sourceTimeZone ) ;
2014-08-13 10:39:27 +01:00
if ( destinationTimeZone ! = TimeZoneInfo . Utc ) {
utc = ConvertTimeFromUtc ( utc , destinationTimeZone ) ;
if ( dateTime . Kind = = DateTimeKind . Unspecified )
return DateTime . SpecifyKind ( utc , DateTimeKind . Unspecified ) ;
}
return utc ;
}
public static DateTimeOffset ConvertTime ( DateTimeOffset dateTimeOffset , TimeZoneInfo destinationTimeZone )
{
if ( destinationTimeZone = = null )
throw new ArgumentNullException ( "destinationTimeZone" ) ;
2015-04-26 19:10:23 +01:00
2014-08-13 10:39:27 +01:00
var utcDateTime = dateTimeOffset . UtcDateTime ;
2015-04-26 19:10:23 +01:00
bool isDst ;
var utcOffset = destinationTimeZone . GetUtcOffset ( utcDateTime , out isDst ) ;
return new DateTimeOffset ( DateTime . SpecifyKind ( utcDateTime , DateTimeKind . Unspecified ) + utcOffset , utcOffset ) ;
2014-08-13 10:39:27 +01:00
}
public static DateTime ConvertTimeBySystemTimeZoneId ( DateTime dateTime , string destinationTimeZoneId )
{
return ConvertTime ( dateTime , FindSystemTimeZoneById ( destinationTimeZoneId ) ) ;
}
public static DateTime ConvertTimeBySystemTimeZoneId ( DateTime dateTime , string sourceTimeZoneId , string destinationTimeZoneId )
{
return ConvertTime ( dateTime , FindSystemTimeZoneById ( sourceTimeZoneId ) , FindSystemTimeZoneById ( destinationTimeZoneId ) ) ;
}
public static DateTimeOffset ConvertTimeBySystemTimeZoneId ( DateTimeOffset dateTimeOffset , string destinationTimeZoneId )
{
return ConvertTime ( dateTimeOffset , FindSystemTimeZoneById ( destinationTimeZoneId ) ) ;
}
private DateTime ConvertTimeFromUtc ( DateTime dateTime )
{
if ( dateTime . Kind = = DateTimeKind . Local )
throw new ArgumentException ( "Kind property of dateTime is Local" ) ;
if ( this = = TimeZoneInfo . Utc )
return DateTime . SpecifyKind ( dateTime , DateTimeKind . Utc ) ;
2015-04-26 19:10:23 +01:00
var utcOffset = GetUtcOffset ( dateTime ) ;
2014-08-13 10:39:27 +01:00
2015-04-26 19:10:23 +01:00
var kind = ( this = = TimeZoneInfo . Local ) ? DateTimeKind . Local : DateTimeKind . Unspecified ;
DateTime result ;
if ( ! TryAddTicks ( dateTime , utcOffset . Ticks , out result , kind ) )
return DateTime . SpecifyKind ( DateTime . MaxValue , kind ) ;
return result ;
2014-08-13 10:39:27 +01:00
}
public static DateTime ConvertTimeFromUtc ( DateTime dateTime , TimeZoneInfo destinationTimeZone )
{
if ( destinationTimeZone = = null )
throw new ArgumentNullException ( "destinationTimeZone" ) ;
return destinationTimeZone . ConvertTimeFromUtc ( dateTime ) ;
}
public static DateTime ConvertTimeToUtc ( DateTime dateTime )
{
if ( dateTime . Kind = = DateTimeKind . Utc )
return dateTime ;
2015-01-13 10:44:36 +00:00
return ConvertTimeToUtc ( dateTime , TimeZoneInfo . Local ) ;
2014-08-13 10:39:27 +01:00
}
2015-04-07 09:35:12 +01:00
static internal DateTime ConvertTimeToUtc ( DateTime dateTime , TimeZoneInfoOptions flags )
{
return ConvertTimeToUtc ( dateTime , TimeZoneInfo . Local , flags ) ;
}
2014-08-13 10:39:27 +01:00
public static DateTime ConvertTimeToUtc ( DateTime dateTime , TimeZoneInfo sourceTimeZone )
{
2015-04-07 09:35:12 +01:00
return ConvertTimeToUtc ( dateTime , sourceTimeZone , TimeZoneInfoOptions . None ) ;
}
2014-08-13 10:39:27 +01:00
2015-04-07 09:35:12 +01:00
static DateTime ConvertTimeToUtc ( DateTime dateTime , TimeZoneInfo sourceTimeZone , TimeZoneInfoOptions flags )
{
if ( ( flags & TimeZoneInfoOptions . NoThrowOnInvalidTime ) = = 0 ) {
if ( sourceTimeZone = = null )
throw new ArgumentNullException ( "sourceTimeZone" ) ;
2014-08-13 10:39:27 +01:00
2015-04-07 09:35:12 +01:00
if ( dateTime . Kind = = DateTimeKind . Utc & & sourceTimeZone ! = TimeZoneInfo . Utc )
throw new ArgumentException ( "Kind property of dateTime is Utc but the sourceTimeZone does not equal TimeZoneInfo.Utc" ) ;
2014-08-13 10:39:27 +01:00
2015-04-07 09:35:12 +01:00
if ( dateTime . Kind = = DateTimeKind . Local & & sourceTimeZone ! = 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" ) ;
}
2014-08-13 10:39:27 +01:00
if ( dateTime . Kind = = DateTimeKind . Utc )
return dateTime ;
2015-04-26 19:10:23 +01:00
bool isDst ;
var utcOffset = sourceTimeZone . GetUtcOffset ( dateTime , out isDst ) ;
DateTime utcDateTime ;
2016-02-22 11:00:01 -05:00
TryAddTicks ( dateTime , - utcOffset . Ticks , out utcDateTime , DateTimeKind . Utc ) ;
2015-04-26 19:10:23 +01:00
return utcDateTime ;
2015-04-07 09:35:12 +01:00
}
static internal TimeSpan GetDateTimeNowUtcOffsetFromUtc ( DateTime time , out Boolean isAmbiguousLocalDst )
{
bool isDaylightSavings ;
return GetUtcOffsetFromUtc ( time , TimeZoneInfo . Local , out isDaylightSavings , out isAmbiguousLocalDst ) ;
2014-08-13 10:39:27 +01:00
}
public static TimeZoneInfo CreateCustomTimeZone ( string id , TimeSpan baseUtcOffset , string displayName , string standardDisplayName )
{
return CreateCustomTimeZone ( id , baseUtcOffset , displayName , standardDisplayName , null , null , true ) ;
}
public static TimeZoneInfo CreateCustomTimeZone ( string id , TimeSpan baseUtcOffset , string displayName , string standardDisplayName , string daylightDisplayName , TimeZoneInfo . AdjustmentRule [ ] adjustmentRules )
{
return CreateCustomTimeZone ( id , baseUtcOffset , displayName , standardDisplayName , daylightDisplayName , adjustmentRules , false ) ;
}
public static TimeZoneInfo CreateCustomTimeZone ( string id , TimeSpan baseUtcOffset , string displayName , string standardDisplayName , string daylightDisplayName , TimeZoneInfo . AdjustmentRule [ ] adjustmentRules , bool disableDaylightSavingTime )
{
return new TimeZoneInfo ( id , baseUtcOffset , displayName , standardDisplayName , daylightDisplayName , adjustmentRules , disableDaylightSavingTime ) ;
}
public override bool Equals ( object obj )
{
return Equals ( obj as TimeZoneInfo ) ;
}
public bool Equals ( TimeZoneInfo other )
{
if ( other = = null )
return false ;
return other . Id = = this . Id & & HasSameRules ( other ) ;
}
public static TimeZoneInfo FindSystemTimeZoneById ( string id )
{
//FIXME: this method should check for cached values in systemTimeZones
if ( id = = null )
throw new ArgumentNullException ( "id" ) ;
#if ! NET_2_1
if ( TimeZoneKey ! = null )
{
2015-08-26 07:17:56 -04:00
if ( id = = "Coordinated Universal Time" )
id = "UTC" ; //windows xp exception for "StandardName" property
2014-08-13 10:39:27 +01:00
RegistryKey key = TimeZoneKey . OpenSubKey ( id , false ) ;
if ( key = = null )
throw new TimeZoneNotFoundException ( ) ;
return FromRegistryKey ( id , key ) ;
}
#endif
// Local requires special logic that already exists in the Local property (bug #326)
if ( id = = "Local" )
return Local ;
2015-08-26 07:17:56 -04:00
return FindSystemTimeZoneByIdCore ( id ) ;
2014-08-13 10:39:27 +01:00
}
#if LIBC
private static TimeZoneInfo FindSystemTimeZoneByFileName ( string id , string filepath )
{
if ( ! File . Exists ( filepath ) )
throw new TimeZoneNotFoundException ( ) ;
using ( FileStream stream = File . OpenRead ( filepath ) ) {
return BuildFromStream ( id , stream ) ;
}
}
#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 AdjustmentRule [ ] GetAdjustmentRules ( )
{
2015-06-10 19:29:21 -04:00
if ( ! supportsDaylightSavingTime | | adjustmentRules = = null )
2014-08-13 10:39:27 +01:00
return new AdjustmentRule [ 0 ] ;
else
return ( AdjustmentRule [ ] ) adjustmentRules . Clone ( ) ;
}
public TimeSpan [ ] GetAmbiguousTimeOffsets ( DateTime dateTime )
{
if ( ! IsAmbiguousTime ( dateTime ) )
throw new ArgumentException ( "dateTime is not an ambiguous time" ) ;
AdjustmentRule rule = GetApplicableRule ( dateTime ) ;
if ( rule ! = null )
return new TimeSpan [ ] { baseUtcOffset , baseUtcOffset + rule . DaylightDelta } ;
else
return new TimeSpan [ ] { baseUtcOffset , baseUtcOffset } ;
}
public TimeSpan [ ] GetAmbiguousTimeOffsets ( DateTimeOffset dateTimeOffset )
{
if ( ! IsAmbiguousTime ( dateTimeOffset ) )
throw new ArgumentException ( "dateTimeOffset is not an ambiguous time" ) ;
throw new NotImplementedException ( ) ;
}
public override int GetHashCode ( )
{
int hash_code = Id . GetHashCode ( ) ;
foreach ( AdjustmentRule rule in GetAdjustmentRules ( ) )
hash_code ^ = rule . GetHashCode ( ) ;
return hash_code ;
}
void ISerializable . GetObjectData ( SerializationInfo info , StreamingContext context )
{
if ( info = = null )
throw new ArgumentNullException ( "info" ) ;
info . AddValue ( "Id" , id ) ;
info . AddValue ( "DisplayName" , displayName ) ;
info . AddValue ( "StandardName" , standardDisplayName ) ;
info . AddValue ( "DaylightName" , daylightDisplayName ) ;
info . AddValue ( "BaseUtcOffset" , baseUtcOffset ) ;
info . AddValue ( "AdjustmentRules" , adjustmentRules ) ;
info . AddValue ( "SupportsDaylightSavingTime" , SupportsDaylightSavingTime ) ;
}
2015-08-26 07:17:56 -04:00
static ReadOnlyCollection < TimeZoneInfo > systemTimeZones ;
2014-08-13 10:39:27 +01:00
public static ReadOnlyCollection < TimeZoneInfo > GetSystemTimeZones ( )
{
if ( systemTimeZones = = null ) {
2015-08-26 07:17:56 -04:00
var tz = new List < TimeZoneInfo > ( ) ;
2016-02-22 11:00:01 -05:00
GetSystemTimeZonesCore ( tz ) ;
2015-08-26 07:17:56 -04:00
Interlocked . CompareExchange ( ref systemTimeZones , new ReadOnlyCollection < TimeZoneInfo > ( tz ) , null ) ;
2014-08-13 10:39:27 +01:00
}
2015-08-26 07:17:56 -04:00
return systemTimeZones ;
2014-08-13 10:39:27 +01:00
}
public TimeSpan GetUtcOffset ( DateTime dateTime )
{
2015-01-13 10:44:36 +00:00
bool isDST ;
return GetUtcOffset ( dateTime , out isDST ) ;
2014-08-13 10:39:27 +01:00
}
public TimeSpan GetUtcOffset ( DateTimeOffset dateTimeOffset )
{
2016-08-03 10:59:49 +00:00
bool isDST ;
return GetUtcOffset ( dateTimeOffset . UtcDateTime , out isDST ) ;
2014-08-13 10:39:27 +01:00
}
2015-01-13 10:44:36 +00:00
private TimeSpan GetUtcOffset ( DateTime dateTime , out bool isDST )
{
isDST = false ;
TimeZoneInfo tz = this ;
if ( dateTime . Kind = = DateTimeKind . Utc )
tz = TimeZoneInfo . Utc ;
if ( dateTime . Kind = = DateTimeKind . Local )
tz = TimeZoneInfo . Local ;
bool isTzDst ;
2015-04-26 19:10:23 +01:00
var tzOffset = GetUtcOffsetHelper ( dateTime , tz , out isTzDst ) ;
2015-01-13 10:44:36 +00:00
if ( tz = = this ) {
isDST = isTzDst ;
return tzOffset ;
}
2015-04-26 19:10:23 +01:00
DateTime utcDateTime ;
if ( ! TryAddTicks ( dateTime , - tzOffset . Ticks , out utcDateTime , DateTimeKind . Utc ) )
2015-01-13 10:44:36 +00:00
return BaseUtcOffset ;
2015-04-26 19:10:23 +01:00
return GetUtcOffsetHelper ( utcDateTime , this , out isDST ) ;
2015-01-13 10:44:36 +00:00
}
2015-04-26 19:10:23 +01:00
// This is an helper method used by the method above, do not use this on its own.
private static TimeSpan GetUtcOffsetHelper ( DateTime dateTime , TimeZoneInfo tz , out bool isDST )
2015-01-13 10:44:36 +00:00
{
if ( dateTime . Kind = = DateTimeKind . Local & & tz ! = TimeZoneInfo . Local )
throw new Exception ( ) ;
isDST = false ;
if ( tz = = TimeZoneInfo . Utc )
return TimeSpan . Zero ;
TimeSpan offset ;
if ( tz . TryGetTransitionOffset ( dateTime , out offset , out isDST ) )
return offset ;
if ( dateTime . Kind = = DateTimeKind . Utc ) {
var utcRule = tz . GetApplicableRule ( dateTime ) ;
if ( utcRule ! = null & & tz . IsInDST ( utcRule , dateTime ) ) {
isDST = true ;
return tz . BaseUtcOffset + utcRule . DaylightDelta ;
}
return tz . BaseUtcOffset ;
}
2015-04-26 19:10:23 +01:00
DateTime stdUtcDateTime ;
if ( ! TryAddTicks ( dateTime , - tz . BaseUtcOffset . Ticks , out stdUtcDateTime , DateTimeKind . Utc ) )
2015-01-13 10:44:36 +00:00
return tz . BaseUtcOffset ;
var tzRule = tz . GetApplicableRule ( stdUtcDateTime ) ;
DateTime dstUtcDateTime = DateTime . MinValue ;
if ( tzRule ! = null ) {
2015-04-26 19:10:23 +01:00
if ( ! TryAddTicks ( stdUtcDateTime , - tzRule . DaylightDelta . Ticks , out dstUtcDateTime , DateTimeKind . Utc ) )
2015-01-13 10:44:36 +00:00
return tz . BaseUtcOffset ;
}
if ( tzRule ! = null & & tz . IsInDST ( tzRule , stdUtcDateTime ) & & tz . IsInDST ( tzRule , dstUtcDateTime ) ) {
isDST = true ;
return tz . BaseUtcOffset + tzRule . DaylightDelta ;
}
return tz . BaseUtcOffset ;
}
2014-08-13 10:39:27 +01:00
public bool HasSameRules ( TimeZoneInfo other )
{
if ( other = = null )
throw new ArgumentNullException ( "other" ) ;
if ( ( this . adjustmentRules = = null ) ! = ( other . adjustmentRules = = null ) )
return false ;
if ( this . adjustmentRules = = null )
return true ;
if ( this . BaseUtcOffset ! = other . BaseUtcOffset )
return false ;
if ( this . adjustmentRules . Length ! = other . adjustmentRules . Length )
return false ;
for ( int i = 0 ; i < adjustmentRules . Length ; i + + ) {
if ( ! ( this . adjustmentRules [ i ] ) . Equals ( other . adjustmentRules [ i ] ) )
return false ;
}
return true ;
}
public bool IsAmbiguousTime ( DateTime dateTime )
{
if ( dateTime . Kind = = DateTimeKind . Local & & IsInvalidTime ( dateTime ) )
throw new ArgumentException ( "Kind is Local and time is Invalid" ) ;
if ( this = = TimeZoneInfo . Utc )
return false ;
if ( dateTime . Kind = = DateTimeKind . Utc )
dateTime = ConvertTimeFromUtc ( dateTime ) ;
if ( dateTime . Kind = = DateTimeKind . Local & & this ! = TimeZoneInfo . Local )
dateTime = ConvertTime ( dateTime , TimeZoneInfo . Local , this ) ;
AdjustmentRule rule = GetApplicableRule ( dateTime ) ;
if ( rule ! = null ) {
DateTime tpoint = TransitionPoint ( rule . DaylightTransitionEnd , dateTime . Year ) ;
if ( dateTime > tpoint - rule . DaylightDelta & & dateTime < = tpoint )
return true ;
}
return false ;
}
public bool IsAmbiguousTime ( DateTimeOffset dateTimeOffset )
{
throw new NotImplementedException ( ) ;
}
2015-01-13 10:44:36 +00:00
private bool IsInDST ( AdjustmentRule rule , DateTime dateTime )
{
// Check whether we're in the dateTime year's DST period
if ( IsInDSTForYear ( rule , dateTime , dateTime . Year ) )
return true ;
// We might be in the dateTime previous year's DST period
2016-08-03 10:59:49 +00:00
return dateTime . Year > 1 & & IsInDSTForYear ( rule , dateTime , dateTime . Year - 1 ) ;
2015-01-13 10:44:36 +00:00
}
2014-08-13 10:39:27 +01:00
bool IsInDSTForYear ( AdjustmentRule rule , DateTime dateTime , int year )
{
DateTime DST_start = TransitionPoint ( rule . DaylightTransitionStart , year ) ;
DateTime DST_end = TransitionPoint ( rule . DaylightTransitionEnd , year + ( ( rule . DaylightTransitionStart . Month < rule . DaylightTransitionEnd . Month ) ? 0 : 1 ) ) ;
if ( dateTime . Kind = = DateTimeKind . Utc ) {
DST_start - = BaseUtcOffset ;
DST_end - = ( BaseUtcOffset + rule . DaylightDelta ) ;
}
return ( dateTime > = DST_start & & dateTime < DST_end ) ;
}
public bool IsDaylightSavingTime ( DateTime dateTime )
{
if ( dateTime . Kind = = DateTimeKind . Local & & IsInvalidTime ( dateTime ) )
throw new ArgumentException ( "dateTime is invalid and Kind is Local" ) ;
if ( this = = TimeZoneInfo . Utc )
return false ;
if ( ! SupportsDaylightSavingTime )
return false ;
2015-01-13 10:44:36 +00:00
bool isDst ;
GetUtcOffset ( dateTime , out isDst ) ;
2014-08-13 10:39:27 +01:00
2015-01-13 10:44:36 +00:00
return isDst ;
2014-08-13 10:39:27 +01:00
}
2015-04-07 09:35:12 +01:00
internal bool IsDaylightSavingTime ( DateTime dateTime , TimeZoneInfoOptions flags )
2014-08-13 10:39:27 +01:00
{
2015-04-07 09:35:12 +01:00
return IsDaylightSavingTime ( dateTime ) ;
2014-08-13 10:39:27 +01:00
}
2015-04-07 09:35:12 +01:00
public bool IsDaylightSavingTime ( DateTimeOffset dateTimeOffset )
2014-08-13 10:39:27 +01:00
{
2015-04-07 09:35:12 +01:00
throw new NotImplementedException ( ) ;
2014-08-13 10:39:27 +01:00
}
2015-08-26 07:17:56 -04:00
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.
2015-09-24 06:06:07 -04:00
if ( ! TryAddTicks ( start , BaseUtcOffset . Ticks , out start ) )
start = DateTime . MinValue ;
2015-08-26 07:17:56 -04:00
// DaylightTime.End is relative to the DST time.
2015-09-24 06:06:07 -04:00
if ( ! TryAddTicks ( end , BaseUtcOffset . Ticks + delta . Ticks , out end ) )
end = DateTime . MinValue ;
2015-08-26 07:17:56 -04:00
} 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 ) ;
}
2015-04-26 19:10:23 +01:00
public bool IsInvalidTime ( DateTime dateTime )
{
if ( dateTime . Kind = = DateTimeKind . Utc )
return false ;
if ( dateTime . Kind = = DateTimeKind . Local & & this ! = Local )
return false ;
AdjustmentRule rule = GetApplicableRule ( dateTime ) ;
if ( rule ! = null ) {
DateTime tpoint = TransitionPoint ( rule . DaylightTransitionStart , dateTime . Year ) ;
if ( dateTime > = tpoint & & dateTime < tpoint + rule . DaylightDelta )
return true ;
}
return false ;
}
2014-08-13 10:39:27 +01:00
void IDeserializationCallback . OnDeserialization ( object sender )
{
try {
TimeZoneInfo . Validate ( id , baseUtcOffset , adjustmentRules ) ;
} catch ( ArgumentException ex ) {
throw new SerializationException ( "invalid serialization data" , ex ) ;
}
}
private static void Validate ( string id , TimeSpan baseUtcOffset , AdjustmentRule [ ] adjustmentRules )
{
if ( id = = null )
throw new ArgumentNullException ( "id" ) ;
if ( id = = String . Empty )
throw new ArgumentException ( "id parameter is an empty string" ) ;
if ( baseUtcOffset . Ticks % TimeSpan . TicksPerMinute ! = 0 )
throw new ArgumentException ( "baseUtcOffset parameter does not represent a whole number of minutes" ) ;
if ( baseUtcOffset > new TimeSpan ( 14 , 0 , 0 ) | | baseUtcOffset < new TimeSpan ( - 14 , 0 , 0 ) )
throw new ArgumentOutOfRangeException ( "baseUtcOffset parameter is greater than 14 hours or less than -14 hours" ) ;
#if STRICT
if ( id . Length > 32 )
throw new ArgumentException ( "id parameter shouldn't be longer than 32 characters" ) ;
#endif
if ( adjustmentRules ! = null & & adjustmentRules . Length ! = 0 ) {
AdjustmentRule prev = null ;
foreach ( AdjustmentRule current in adjustmentRules ) {
if ( current = = null )
throw new InvalidTimeZoneException ( "one or more elements in adjustmentRules are null" ) ;
if ( ( baseUtcOffset + current . DaylightDelta < new TimeSpan ( - 14 , 0 , 0 ) ) | |
( baseUtcOffset + current . DaylightDelta > new TimeSpan ( 14 , 0 , 0 ) ) )
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;" ) ;
if ( prev ! = null & & prev . DateStart > current . DateStart )
throw new InvalidTimeZoneException ( "adjustment rules specified in adjustmentRules parameter are not in chronological order" ) ;
if ( prev ! = null & & prev . DateEnd > current . DateStart )
throw new InvalidTimeZoneException ( "some adjustment rules in the adjustmentRules parameter overlap" ) ;
if ( prev ! = null & & prev . DateEnd = = current . DateStart )
throw new InvalidTimeZoneException ( "a date can have multiple adjustment rules applied to it" ) ;
prev = current ;
}
}
}
public override string ToString ( )
{
return DisplayName ;
}
private TimeZoneInfo ( SerializationInfo info , StreamingContext context )
{
if ( info = = null )
throw new ArgumentNullException ( "info" ) ;
id = ( string ) info . GetValue ( "Id" , typeof ( string ) ) ;
displayName = ( string ) info . GetValue ( "DisplayName" , typeof ( string ) ) ;
standardDisplayName = ( string ) info . GetValue ( "StandardName" , typeof ( string ) ) ;
daylightDisplayName = ( string ) info . GetValue ( "DaylightName" , typeof ( string ) ) ;
baseUtcOffset = ( TimeSpan ) info . GetValue ( "BaseUtcOffset" , typeof ( TimeSpan ) ) ;
adjustmentRules = ( TimeZoneInfo . AdjustmentRule [ ] ) info . GetValue ( "AdjustmentRules" , typeof ( TimeZoneInfo . AdjustmentRule [ ] ) ) ;
supportsDaylightSavingTime = ( bool ) info . GetValue ( "SupportsDaylightSavingTime" , typeof ( bool ) ) ;
}
private TimeZoneInfo ( string id , TimeSpan baseUtcOffset , string displayName , string standardDisplayName , string daylightDisplayName , TimeZoneInfo . AdjustmentRule [ ] adjustmentRules , bool disableDaylightSavingTime )
{
if ( id = = null )
throw new ArgumentNullException ( "id" ) ;
if ( id = = String . Empty )
throw new ArgumentException ( "id parameter is an empty string" ) ;
if ( baseUtcOffset . Ticks % TimeSpan . TicksPerMinute ! = 0 )
throw new ArgumentException ( "baseUtcOffset parameter does not represent a whole number of minutes" ) ;
if ( baseUtcOffset > new TimeSpan ( 14 , 0 , 0 ) | | baseUtcOffset < new TimeSpan ( - 14 , 0 , 0 ) )
throw new ArgumentOutOfRangeException ( "baseUtcOffset parameter is greater than 14 hours or less than -14 hours" ) ;
#if STRICT
if ( id . Length > 32 )
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 ) {
if ( current = = null )
throw new InvalidTimeZoneException ( "one or more elements in adjustmentRules are null" ) ;
if ( ( baseUtcOffset + current . DaylightDelta < new TimeSpan ( - 14 , 0 , 0 ) ) | |
( baseUtcOffset + current . DaylightDelta > new TimeSpan ( 14 , 0 , 0 ) ) )
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;" ) ;
if ( prev ! = null & & prev . DateStart > current . DateStart )
throw new InvalidTimeZoneException ( "adjustment rules specified in adjustmentRules parameter are not in chronological order" ) ;
if ( prev ! = null & & prev . DateEnd > current . DateStart )
throw new InvalidTimeZoneException ( "some adjustment rules in the adjustmentRules parameter overlap" ) ;
if ( prev ! = null & & prev . DateEnd = = current . DateStart )
throw new InvalidTimeZoneException ( "a date can have multiple adjustment rules applied to it" ) ;
prev = current ;
}
} else {
supportsDaylightSavingTime = false ;
}
this . id = id ;
this . baseUtcOffset = baseUtcOffset ;
this . displayName = displayName ? ? id ;
this . standardDisplayName = standardDisplayName ? ? id ;
this . daylightDisplayName = daylightDisplayName ;
this . supportsDaylightSavingTime = supportsDaylightSavingTime ;
this . adjustmentRules = adjustmentRules ;
}
private AdjustmentRule GetApplicableRule ( DateTime dateTime )
{
2015-04-07 09:35:12 +01:00
//Applicable rules are in standard time
2014-08-13 10:39:27 +01:00
DateTime date = dateTime ;
2015-04-26 19:10:23 +01:00
if ( dateTime . Kind = = DateTimeKind . Local & & this ! = TimeZoneInfo . Local ) {
if ( ! TryAddTicks ( date . ToUniversalTime ( ) , BaseUtcOffset . Ticks , out date ) )
return null ;
} else if ( dateTime . Kind = = DateTimeKind . Utc & & this ! = TimeZoneInfo . Utc ) {
if ( ! TryAddTicks ( date , BaseUtcOffset . Ticks , out date ) )
return null ;
}
2014-08-13 10:39:27 +01:00
2015-04-07 09:35:12 +01:00
// get the date component of the datetime
date = date . Date ;
2014-08-13 10:39:27 +01:00
if ( adjustmentRules ! = null ) {
foreach ( AdjustmentRule rule in adjustmentRules ) {
2015-04-07 09:35:12 +01:00
if ( rule . DateStart > date )
2014-08-13 10:39:27 +01:00
return null ;
2015-04-07 09:35:12 +01:00
if ( rule . DateEnd < date )
2014-08-13 10:39:27 +01:00
continue ;
return rule ;
}
}
return null ;
}
2015-01-13 10:44:36 +00:00
private bool TryGetTransitionOffset ( DateTime dateTime , out TimeSpan offset , out bool isDst )
{
offset = BaseUtcOffset ;
isDst = false ;
if ( transitions = = null )
return false ;
2015-04-07 09:35:12 +01:00
//Transitions are in UTC
2015-01-13 10:44:36 +00:00
DateTime date = dateTime ;
2015-04-07 09:35:12 +01:00
if ( dateTime . Kind = = DateTimeKind . Local & & this ! = TimeZoneInfo . Local ) {
2015-04-26 19:10:23 +01:00
if ( ! TryAddTicks ( date . ToUniversalTime ( ) , BaseUtcOffset . Ticks , out date , DateTimeKind . Utc ) )
2015-04-07 09:35:12 +01:00
return false ;
}
if ( dateTime . Kind ! = DateTimeKind . Utc ) {
2015-04-26 19:10:23 +01:00
if ( ! TryAddTicks ( date , - BaseUtcOffset . Ticks , out date , DateTimeKind . Utc ) )
2015-04-07 09:35:12 +01:00
return false ;
}
2015-01-13 10:44:36 +00:00
for ( var i = transitions . Count - 1 ; i > = 0 ; i - - ) {
var pair = transitions [ i ] ;
DateTime ttime = pair . Key ;
TimeType ttype = pair . Value ;
if ( ttime > date )
continue ;
offset = new TimeSpan ( 0 , 0 , ttype . Offset ) ;
isDst = ttype . IsDst ;
return true ;
}
return false ;
}
2014-08-13 10:39:27 +01:00
private static DateTime TransitionPoint ( TransitionTime transition , int year )
{
if ( transition . IsFixedDateRule )
return new DateTime ( year , transition . Month , transition . Day ) + transition . TimeOfDay . TimeOfDay ;
DayOfWeek first = ( new DateTime ( year , transition . Month , 1 ) ) . DayOfWeek ;
2015-04-07 09:35:12 +01:00
int day = 1 + ( transition . Week - 1 ) * 7 + ( transition . DayOfWeek - first + 7 ) % 7 ;
2014-08-13 10:39:27 +01:00
if ( day > DateTime . DaysInMonth ( year , transition . Month ) )
day - = 7 ;
2014-10-04 11:27:48 +01:00
if ( day < 1 )
day + = 7 ;
2014-08-13 10:39:27 +01:00
return new DateTime ( year , transition . Month , day ) + transition . TimeOfDay . TimeOfDay ;
}
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 ;
}
2015-08-26 07:17:56 -04:00
#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 ) ;
2016-08-03 10:59:49 +00:00
} catch ( InvalidTimeZoneException ) {
throw ;
2015-08-26 07:17:56 -04:00
} catch ( Exception e ) {
2016-08-03 10:59:49 +00:00
throw new InvalidTimeZoneException ( "Time zone information file contains invalid data" , e ) ;
2015-08-26 07:17:56 -04:00
}
}
2014-08-13 10:39:27 +01:00
private static bool ValidTZFile ( byte [ ] buffer , int length )
{
StringBuilder magic = new StringBuilder ( ) ;
for ( int i = 0 ; i < 4 ; i + + )
magic . Append ( ( char ) buffer [ i ] ) ;
if ( magic . ToString ( ) ! = "TZif" )
return false ;
if ( length > = BUFFER_SIZE )
return false ;
return true ;
}
static int SwapInt32 ( int i )
{
return ( ( ( i > > 24 ) & 0xff )
| ( ( i > > 8 ) & 0xff00 )
| ( ( i < < 8 ) & 0xff0000 )
2016-02-22 11:00:01 -05:00
| ( ( ( i & 0xff ) < < 24 ) ) ) ;
2014-08-13 10:39:27 +01:00
}
static int ReadBigEndianInt32 ( byte [ ] buffer , int start )
{
int i = BitConverter . ToInt32 ( buffer , start ) ;
if ( ! BitConverter . IsLittleEndian )
return i ;
return SwapInt32 ( i ) ;
}
private static TimeZoneInfo ParseTZBuffer ( string id , byte [ ] buffer , int length )
{
//Reading the header. 4 bytes for magic, 16 are reserved
int ttisgmtcnt = ReadBigEndianInt32 ( buffer , 20 ) ;
int ttisstdcnt = ReadBigEndianInt32 ( buffer , 24 ) ;
int leapcnt = ReadBigEndianInt32 ( buffer , 28 ) ;
int timecnt = ReadBigEndianInt32 ( buffer , 32 ) ;
int typecnt = ReadBigEndianInt32 ( buffer , 36 ) ;
int charcnt = ReadBigEndianInt32 ( buffer , 40 ) ;
if ( length < 44 + timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisgmtcnt )
throw new InvalidTimeZoneException ( ) ;
Dictionary < int , string > abbreviations = ParseAbbreviations ( buffer , 44 + 4 * timecnt + timecnt + 6 * typecnt , charcnt ) ;
Dictionary < int , TimeType > time_types = ParseTimesTypes ( buffer , 44 + 4 * timecnt + timecnt , typecnt , abbreviations ) ;
List < KeyValuePair < DateTime , TimeType > > transitions = ParseTransitions ( buffer , 44 , timecnt , time_types ) ;
if ( time_types . Count = = 0 )
throw new InvalidTimeZoneException ( ) ;
2016-08-03 10:59:49 +00:00
if ( time_types . Count = = 1 & & time_types [ 0 ] . IsDst )
2014-08-13 10:39:27 +01:00
throw new InvalidTimeZoneException ( ) ;
TimeSpan baseUtcOffset = new TimeSpan ( 0 ) ;
TimeSpan dstDelta = new TimeSpan ( 0 ) ;
string standardDisplayName = null ;
string daylightDisplayName = null ;
bool dst_observed = false ;
DateTime dst_start = DateTime . MinValue ;
List < AdjustmentRule > adjustmentRules = new List < AdjustmentRule > ( ) ;
2015-01-13 10:44:36 +00:00
bool storeTransition = false ;
2014-08-13 10:39:27 +01:00
for ( int i = 0 ; i < transitions . Count ; i + + ) {
var pair = transitions [ i ] ;
DateTime ttime = pair . Key ;
TimeType ttype = pair . Value ;
if ( ! ttype . IsDst ) {
2015-04-07 09:35:12 +01:00
if ( standardDisplayName ! = ttype . Name )
2014-08-13 10:39:27 +01:00
standardDisplayName = ttype . Name ;
2015-04-07 09:35:12 +01:00
if ( baseUtcOffset . TotalSeconds ! = ttype . Offset ) {
2014-08-13 10:39:27 +01:00
baseUtcOffset = new TimeSpan ( 0 , 0 , ttype . Offset ) ;
2015-01-13 10:44:36 +00:00
if ( adjustmentRules . Count > 0 ) // We ignore AdjustmentRules but store transitions.
storeTransition = true ;
2014-08-13 10:39:27 +01:00
adjustmentRules = new List < AdjustmentRule > ( ) ;
dst_observed = false ;
}
if ( dst_observed ) {
//FIXME: check additional fields for this:
//most of the transitions are expressed in GMT
dst_start + = baseUtcOffset ;
DateTime dst_end = ttime + baseUtcOffset + dstDelta ;
//some weird timezone (America/Phoenix) have end dates on Jan 1st
if ( dst_end . Date = = new DateTime ( dst_end . Year , 1 , 1 ) & & dst_end . Year > dst_start . Year )
dst_end - = new TimeSpan ( 24 , 0 , 0 ) ;
2015-04-26 19:10:23 +01:00
/ *
* AdjustmentRule specifies a DST period that starts and ends within a year .
* When we have a DST period longer than a year , the generated AdjustmentRule may not be usable .
* Thus we fallback to the transitions .
* /
if ( dst_start . AddYears ( 1 ) < dst_end )
storeTransition = true ;
2014-08-13 10:39:27 +01:00
DateTime dateStart , dateEnd ;
if ( dst_start . Month < 7 )
dateStart = new DateTime ( dst_start . Year , 1 , 1 ) ;
else
dateStart = new DateTime ( dst_start . Year , 7 , 1 ) ;
if ( dst_end . Month > = 7 )
dateEnd = new DateTime ( dst_end . Year , 12 , 31 ) ;
else
dateEnd = new DateTime ( dst_end . Year , 6 , 30 ) ;
TransitionTime transition_start = TransitionTime . CreateFixedDateRule ( new DateTime ( 1 , 1 , 1 ) + dst_start . TimeOfDay , dst_start . Month , dst_start . Day ) ;
TransitionTime transition_end = TransitionTime . CreateFixedDateRule ( new DateTime ( 1 , 1 , 1 ) + dst_end . TimeOfDay , dst_end . Month , dst_end . Day ) ;
if ( transition_start ! = transition_end ) //y, that happened in Argentina in 1943-1946
adjustmentRules . Add ( AdjustmentRule . CreateAdjustmentRule ( dateStart , dateEnd , dstDelta , transition_start , transition_end ) ) ;
}
dst_observed = false ;
} else {
2015-04-07 09:35:12 +01:00
if ( daylightDisplayName ! = ttype . Name )
2014-08-13 10:39:27 +01:00
daylightDisplayName = ttype . Name ;
2015-08-26 07:17:56 -04:00
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 ) ) ;
}
2015-04-07 09:35:12 +01:00
2014-08-13 10:39:27 +01:00
dst_start = ttime ;
dst_observed = true ;
}
}
2015-01-13 10:44:36 +00:00
TimeZoneInfo tz ;
if ( adjustmentRules . Count = = 0 & & ! storeTransition ) {
2014-08-13 10:39:27 +01:00
if ( standardDisplayName = = null ) {
2016-08-03 10:59:49 +00:00
var t = time_types [ 0 ] ;
2014-08-13 10:39:27 +01:00
standardDisplayName = t . Name ;
baseUtcOffset = new TimeSpan ( 0 , 0 , t . Offset ) ;
}
2015-01-13 10:44:36 +00:00
tz = CreateCustomTimeZone ( id , baseUtcOffset , id , standardDisplayName ) ;
2014-08-13 10:39:27 +01:00
} else {
2015-01-13 10:44:36 +00:00
tz = CreateCustomTimeZone ( id , baseUtcOffset , id , standardDisplayName , daylightDisplayName , ValidateRules ( adjustmentRules ) . ToArray ( ) ) ;
2014-08-13 10:39:27 +01:00
}
2015-01-13 10:44:36 +00:00
2015-06-10 19:29:21 -04:00
if ( storeTransition & & transitions . Count > 0 ) {
2015-01-13 10:44:36 +00:00
tz . transitions = transitions ;
2015-06-10 19:29:21 -04:00
tz . supportsDaylightSavingTime = true ;
}
2015-01-13 10:44:36 +00:00
return tz ;
2014-08-13 10:39:27 +01:00
}
static Dictionary < int , string > ParseAbbreviations ( byte [ ] buffer , int index , int count )
{
var abbrevs = new Dictionary < int , string > ( ) ;
int abbrev_index = 0 ;
var sb = new StringBuilder ( ) ;
for ( int i = 0 ; i < count ; i + + ) {
char c = ( char ) buffer [ index + i ] ;
if ( c ! = '\0' )
sb . Append ( c ) ;
else {
abbrevs . Add ( abbrev_index , sb . ToString ( ) ) ;
//Adding all the substrings too, as it seems to be used, at least for Africa/Windhoek
2015-12-09 05:54:53 -05:00
//j == sb.Length empty substring also needs to be added #31432
for ( int j = 1 ; j < = sb . Length ; j + + )
2014-08-13 10:39:27 +01:00
abbrevs . Add ( abbrev_index + j , sb . ToString ( j , sb . Length - j ) ) ;
abbrev_index = i + 1 ;
sb = new StringBuilder ( ) ;
}
}
return abbrevs ;
}
static Dictionary < int , TimeType > ParseTimesTypes ( byte [ ] buffer , int index , int count , Dictionary < int , string > abbreviations )
{
var types = new Dictionary < int , TimeType > ( count ) ;
for ( int i = 0 ; i < count ; i + + ) {
int offset = ReadBigEndianInt32 ( buffer , index + 6 * i ) ;
2016-08-03 10:59:49 +00:00
//
// The official tz database contains timezone with GMT offsets
// not only in whole hours/minutes but in seconds. This happens for years
// before 1901. For example
//
// NAME GMTOFF RULES FORMAT UNTIL
// Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
//
// .NET as of 4.6.2 cannot handle that and uses hours/minutes only, so
// we remove seconds to not crash later
//
offset = ( offset / 60 ) * 60 ;
2014-08-13 10:39:27 +01:00
byte is_dst = buffer [ index + 6 * i + 4 ] ;
byte abbrev = buffer [ index + 6 * i + 5 ] ;
types . Add ( i , new TimeType ( offset , ( is_dst ! = 0 ) , abbreviations [ ( int ) abbrev ] ) ) ;
}
return types ;
}
static List < KeyValuePair < DateTime , TimeType > > ParseTransitions ( byte [ ] buffer , int index , int count , Dictionary < int , TimeType > time_types )
{
var list = new List < KeyValuePair < DateTime , TimeType > > ( count ) ;
for ( int i = 0 ; i < count ; i + + ) {
int unixtime = ReadBigEndianInt32 ( buffer , index + 4 * i ) ;
DateTime ttime = DateTimeFromUnixTime ( unixtime ) ;
byte ttype = buffer [ index + 4 * count + i ] ;
list . Add ( new KeyValuePair < DateTime , TimeType > ( ttime , time_types [ ( int ) ttype ] ) ) ;
}
return list ;
}
static DateTime DateTimeFromUnixTime ( long unix_time )
{
DateTime date_time = new DateTime ( 1970 , 1 , 1 ) ;
return date_time . AddSeconds ( unix_time ) ;
}
2015-04-07 09:35:12 +01:00
#region reference sources
2015-04-26 19:10:23 +01:00
// Shortcut for TimeZoneInfo.Local.GetUtcOffset
internal static TimeSpan GetLocalUtcOffset ( DateTime dateTime , TimeZoneInfoOptions flags )
{
bool dst ;
return Local . GetUtcOffset ( dateTime , out dst ) ;
}
internal TimeSpan GetUtcOffset ( DateTime dateTime , TimeZoneInfoOptions flags )
{
bool dst ;
return GetUtcOffset ( dateTime , out dst ) ;
}
static internal TimeSpan GetUtcOffsetFromUtc ( DateTime time , TimeZoneInfo zone , out Boolean isDaylightSavings , out Boolean isAmbiguousLocalDst )
{
isDaylightSavings = false ;
isAmbiguousLocalDst = false ;
TimeSpan baseOffset = zone . BaseUtcOffset ;
if ( zone . IsAmbiguousTime ( time ) ) {
isAmbiguousLocalDst = true ;
return baseOffset ;
}
return zone . GetUtcOffset ( time , out isDaylightSavings ) ;
}
2015-04-07 09:35:12 +01:00
#endregion
2014-08-13 10:39:27 +01:00
}
2016-08-03 10:59:49 +00:00
class TimeType {
2014-08-13 10:39:27 +01:00
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
}
}