From 76478fccdc83e6c37a82e01aef24fb2cce082378 Mon Sep 17 00:00:00 2001 From: Fadi Hanna Date: Fri, 19 Apr 2019 14:10:09 -0700 Subject: [PATCH 01/24] Fix project metadata in test (#24119) * Fix project metadata in test --- .../rvastatics/RVAOrderingTest.ilproj | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/src/JIT/Directed/rvastatics/RVAOrderingTest.ilproj b/tests/src/JIT/Directed/rvastatics/RVAOrderingTest.ilproj index a961e563f862..9424692486d2 100644 --- a/tests/src/JIT/Directed/rvastatics/RVAOrderingTest.ilproj +++ b/tests/src/JIT/Directed/rvastatics/RVAOrderingTest.ilproj @@ -2,6 +2,8 @@ + Debug + AnyCPU $(MSBuildProjectName) 2.0 {95DFC527-4DC1-495E-97D7-E94EE1F7140D} @@ -10,9 +12,24 @@ ..\..\ 1 + + + + + PdbOnly + True + + + + False + + + + + - \ No newline at end of file + From 533564b6f241ee733b30e71ab4f5fbb758c30aad Mon Sep 17 00:00:00 2001 From: Fadi Hanna Date: Fri, 19 Apr 2019 14:17:18 -0700 Subject: [PATCH 02/24] Exclude failing tests on all platforms/architectures (#24121) --- tests/issues.targets | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/issues.targets b/tests/issues.targets index 0a1103ddfc2b..e88f0b692c86 100644 --- a/tests/issues.targets +++ b/tests/issues.targets @@ -74,6 +74,12 @@ 20299 + + 23941 + + + 23941 + @@ -298,12 +304,6 @@ Varargs supported on this platform - - 23941 - - - 23941 - From 4029007b7edd290c236901a246ef48ac3e9de4b6 Mon Sep 17 00:00:00 2001 From: Mukul Sabharwal Date: Fri, 19 Apr 2019 14:54:35 -0700 Subject: [PATCH 03/24] Large Pages on Linux & macOS (#24098) --- src/gc/unix/config.h.in | 1 + src/gc/unix/configure.cmake | 9 +++++++++ src/gc/unix/gcenv.unix.cpp | 2 ++ src/pal/src/config.h.in | 2 ++ src/pal/src/configure.cmake | 16 ++++++++++++++++ src/pal/src/map/virtual.cpp | 29 ++++++++++++++++++++++------- 6 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/gc/unix/config.h.in b/src/gc/unix/config.h.in index 99866cdadc50..7f4d8d766c01 100644 --- a/src/gc/unix/config.h.in +++ b/src/gc/unix/config.h.in @@ -9,6 +9,7 @@ #cmakedefine01 HAVE_SYS_MMAN_H #cmakedefine01 HAVE_PTHREAD_THREADID_NP #cmakedefine01 HAVE_PTHREAD_GETTHREADID_NP +#cmakedefine01 HAVE_VM_FLAGS_SUPERPAGE_SIZE_ANY #cmakedefine01 HAVE_MAP_HUGETLB #cmakedefine01 HAVE_SCHED_GETCPU #cmakedefine01 HAVE_NUMA_H diff --git a/src/gc/unix/configure.cmake b/src/gc/unix/configure.cmake index 2e317666127f..3e478c4e6bb1 100644 --- a/src/gc/unix/configure.cmake +++ b/src/gc/unix/configure.cmake @@ -24,6 +24,15 @@ check_cxx_source_compiles(" } " HAVE_PTHREAD_GETTHREADID_NP) +check_cxx_source_compiles(" + #include + + int main() + { + return VM_FLAGS_SUPERPAGE_SIZE_ANY; + } + " HAVE_VM_FLAGS_SUPERPAGE_SIZE_ANY) + check_cxx_source_compiles(" #include diff --git a/src/gc/unix/gcenv.unix.cpp b/src/gc/unix/gcenv.unix.cpp index edddee16ce24..c248082c959a 100644 --- a/src/gc/unix/gcenv.unix.cpp +++ b/src/gc/unix/gcenv.unix.cpp @@ -551,6 +551,8 @@ void* GCToOSInterface::VirtualReserveAndCommitLargePages(size_t size) { #if HAVE_MAP_HUGETLB uint32_t largePagesFlag = MAP_HUGETLB; +#elif HAVE_VM_FLAGS_SUPERPAGE_SIZE_ANY + uint32_t largePagesFlag = VM_FLAGS_SUPERPAGE_SIZE_ANY; #else uint32_t largePagesFlag = 0; #endif diff --git a/src/pal/src/config.h.in b/src/pal/src/config.h.in index d0e14df13a46..3ceb18056317 100644 --- a/src/pal/src/config.h.in +++ b/src/pal/src/config.h.in @@ -1,6 +1,8 @@ #ifndef _PAL_CONFIG_H_INCLUDED #define _PAL_CONFIG_H_INCLUDED 1 +#cmakedefine01 HAVE_VM_FLAGS_SUPERPAGE_SIZE_ANY +#cmakedefine01 HAVE_MAP_HUGETLB #cmakedefine01 HAVE_IEEEFP_H #cmakedefine01 HAVE_SYS_VMPARAM_H #cmakedefine01 HAVE_MACH_VM_TYPES_H diff --git a/src/pal/src/configure.cmake b/src/pal/src/configure.cmake index a4d550e03b3b..a991e7515b23 100644 --- a/src/pal/src/configure.cmake +++ b/src/pal/src/configure.cmake @@ -44,6 +44,22 @@ endif() set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) +check_cxx_source_compiles(" +#include +int main() +{ + return VM_FLAGS_SUPERPAGE_SIZE_ANY; +} +" HAVE_VM_FLAGS_SUPERPAGE_SIZE_ANY) + +check_cxx_source_compiles(" +#include +int main() +{ + return MAP_HUGETLB; +} +" HAVE_MAP_HUGETLB) + check_cxx_source_compiles(" #include int main(int argc, char **argv) { diff --git a/src/pal/src/map/virtual.cpp b/src/pal/src/map/virtual.cpp index 4dae9f31e358..ff5cde94ad6f 100644 --- a/src/pal/src/map/virtual.cpp +++ b/src/pal/src/map/virtual.cpp @@ -70,7 +70,8 @@ static size_t s_virtualPageSize = 0; static LPVOID ReserveVirtualMemory( IN CPalThread *pthrCurrent, /* Currently executing thread */ IN LPVOID lpAddress, /* Region to reserve or commit */ - IN SIZE_T dwSize); /* Size of Region */ + IN SIZE_T dwSize, /* Size of Region */ + IN DWORD fAllocationType); /* Allocation Type */ // A memory allocator that allocates memory from a pre-reserved region @@ -915,7 +916,7 @@ static LPVOID VIRTUALReserveMemory( if (pRetVal == NULL) { // Try to reserve memory from the OS - pRetVal = ReserveVirtualMemory(pthrCurrent, (LPVOID)StartBoundary, MemSize); + pRetVal = ReserveVirtualMemory(pthrCurrent, (LPVOID)StartBoundary, MemSize, flAllocationType); } if (pRetVal != NULL) @@ -958,7 +959,8 @@ static LPVOID VIRTUALReserveMemory( static LPVOID ReserveVirtualMemory( IN CPalThread *pthrCurrent, /* Currently executing thread */ IN LPVOID lpAddress, /* Region to reserve or commit */ - IN SIZE_T dwSize) /* Size of Region */ + IN SIZE_T dwSize, /* Size of Region */ + IN DWORD fAllocationType) /* Allocation type */ { UINT_PTR StartBoundary = (UINT_PTR)lpAddress; SIZE_T MemSize = dwSize; @@ -986,6 +988,19 @@ static LPVOID ReserveVirtualMemory( mmapFlags |= MAP_FIXED; #endif // HAVE_VM_ALLOCATE + if ((fAllocationType & MEM_LARGE_PAGES) != 0) + { +#if HAVE_MAP_HUGETLB + mmapFlags |= MAP_HUGETLB; + TRACE("MAP_HUGETLB flag set\n"); +#elif HAVE_VM_FLAGS_SUPERPAGE_SIZE_ANY + mmapFlags |= VM_FLAGS_SUPERPAGE_SIZE_ANY; + TRACE("VM_FLAGS_SUPERPAGE_SIZE_ANY flag set\n"); +#else + TRACE("Large Pages requested, but not supported in this PAL configuration\n"); +#endif + } + mmapFlags |= MAP_ANON | MAP_PRIVATE; LPVOID pRetVal = mmap((LPVOID) StartBoundary, @@ -1338,10 +1353,10 @@ VirtualAlloc( } /* Test for un-supported flags. */ - if ( ( flAllocationType & ~( MEM_COMMIT | MEM_RESERVE | MEM_RESET | MEM_TOP_DOWN | MEM_RESERVE_EXECUTABLE ) ) != 0 ) + if ( ( flAllocationType & ~( MEM_COMMIT | MEM_RESERVE | MEM_RESET | MEM_TOP_DOWN | MEM_RESERVE_EXECUTABLE | MEM_LARGE_PAGES ) ) != 0 ) { ASSERT( "flAllocationType can be one, or any combination of MEM_COMMIT, \ - MEM_RESERVE, MEM_TOP_DOWN, or MEM_RESERVE_EXECUTABLE.\n" ); + MEM_RESERVE, MEM_TOP_DOWN, MEM_RESERVE_EXECUTABLE, or MEM_LARGE_PAGES.\n" ); pthrCurrent->SetLastError( ERROR_INVALID_PARAMETER ); goto done; } @@ -2145,7 +2160,7 @@ void ExecutableMemoryAllocator::TryReserveInitialMemory() // Do actual memory reservation. do { - m_startAddress = ReserveVirtualMemory(pthrCurrent, (void*)preferredStartAddress, sizeOfAllocation); + m_startAddress = ReserveVirtualMemory(pthrCurrent, (void*)preferredStartAddress, sizeOfAllocation, 0 /* fAllocationType */); if (m_startAddress != nullptr) { break; @@ -2175,7 +2190,7 @@ void ExecutableMemoryAllocator::TryReserveInitialMemory() // - The code heap allocator for the JIT can allocate from this address space. Beyond this reservation, one can use // the COMPlus_CodeHeapReserveForJumpStubs environment variable to reserve space for jump stubs. sizeOfAllocation = MaxExecutableMemorySize; - m_startAddress = ReserveVirtualMemory(pthrCurrent, nullptr, sizeOfAllocation); + m_startAddress = ReserveVirtualMemory(pthrCurrent, nullptr, sizeOfAllocation, 0 /* fAllocationType */); if (m_startAddress == nullptr) { return; From 6a7bf9c3af1d3d6e78d7247eef07c48c89a57112 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Fri, 19 Apr 2019 15:49:32 -0700 Subject: [PATCH 04/24] [master] Update dependencies from dotnet/corefx (#24058) * Update dependencies from https://github.com/dotnet/corefx build 20190418.11 - Microsoft.NETCore.Platforms - 3.0.0-preview5.19218.11 - Microsoft.Private.CoreFx.NETCoreApp - 4.6.0-preview5.19218.11 * Disable outdated test --- eng/Version.Details.xml | 8 ++++---- eng/Versions.props | 4 ++-- tests/CoreFX/CoreFX.issues.json | 4 ++++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 99ca64884df2..557bb50f8f9f 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -11,13 +11,13 @@ https://github.com/dotnet/arcade 517bf671ea342965d007aa48f5bfd4926e58d582 - + https://github.com/dotnet/corefx - 071e6986b2a71e29cab3df4e2dd2cdf3fe3ce574 + 0a9a366e290aded3e9bf5d082d6beee5ff560177 - + https://github.com/dotnet/corefx - 071e6986b2a71e29cab3df4e2dd2cdf3fe3ce574 + 0a9a366e290aded3e9bf5d082d6beee5ff560177 https://github.com/dotnet/core-setup diff --git a/eng/Versions.props b/eng/Versions.props index a3d93b649d0f..11412f26ab02 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -9,8 +9,8 @@ true 2.5.1-beta.19179.4 1.0.0-alpha-004 - 4.6.0-preview5.19215.16 - 3.0.0-preview5.19215.16 + 4.6.0-preview5.19218.11 + 3.0.0-preview5.19218.11 3.0.0-preview5-27618-16 99.99.99-master-20190313.3 99.99.99-master-20190313.3 diff --git a/tests/CoreFX/CoreFX.issues.json b/tests/CoreFX/CoreFX.issues.json index 78960e96ffc3..b0eb61c7d44d 100644 --- a/tests/CoreFX/CoreFX.issues.json +++ b/tests/CoreFX/CoreFX.issues.json @@ -1062,6 +1062,10 @@ "namespaces": null, "classes": null, "methods": [ + { + "name": "System.Runtime.InteropServices.HandleCollectorTests.Ctor_InitialThresholdGreaterThanMaximumThreshold_ThrowsArgumentException ", + "reason": "outdated" + }, { "name": "System.Runtime.InteropServices.Tests.GCHandleTests.Alloc_InvalidPinnedObject_ThrowsArgumentException", "reason": "outdated" From 3bd49adadf30d9437efd0b592518ad64fc6c8c5c Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Fri, 19 Apr 2019 16:15:34 -0700 Subject: [PATCH 05/24] Fix Arm32 Ubuntu CoreFX script. --- tests/scripts/run-corefx-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/scripts/run-corefx-tests.py b/tests/scripts/run-corefx-tests.py index 594d6c4c19b6..0222953fbbe8 100644 --- a/tests/scripts/run-corefx-tests.py +++ b/tests/scripts/run-corefx-tests.py @@ -299,7 +299,7 @@ def main(args): if not Is_windows and arch == 'arm' : # We need to force clang5.0; we are building in a docker container that doesn't have # clang3.9, which is currently the default used by the native build. - common_config_args += ' /p:BuildNativeClang=--clang5.0' + common_config_args += ' /p:BuildNativeCompiler=--clang5.0' if not Is_windows and (arch == 'arm' or arch == 'arm64'): # It is needed under docker where LC_ALL is not configured. From b8916d5c8be8bd9701fa36b875f10721bde8ce16 Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Fri, 19 Apr 2019 15:49:39 -0700 Subject: [PATCH 06/24] Fix `impGetStructAddr`. --- src/jit/importer.cpp | 46 +++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp index db6b6df5718f..ba7eaa357daf 100644 --- a/src/jit/importer.cpp +++ b/src/jit/importer.cpp @@ -687,18 +687,30 @@ GenTreeStmt* Compiler::impExtractLastStmt() return stmt; } -/***************************************************************************** - * - * Insert the given GT_STMT "stmt" before GT_STMT "stmtBefore" - */ - +//------------------------------------------------------------------------- +// impInsertStmtBefore: Insert the given GT_STMT "stmt" before GT_STMT "stmtBefore". +// +// Arguments: +// stmt - a statement to insert; +// stmtBefore - an insertion point to insert "stmt" before. +// inline void Compiler::impInsertStmtBefore(GenTreeStmt* stmt, GenTreeStmt* stmtBefore) { - GenTreeStmt* stmtPrev = stmtBefore->getPrevStmt(); - stmt->gtPrev = stmtPrev; - stmt->gtNext = stmtBefore; - stmtPrev->gtNext = stmt; - stmtBefore->gtPrev = stmt; + assert(stmt != nullptr); + assert(stmtBefore != nullptr); + + if (stmtBefore == impStmtList) + { + impStmtList = stmt; + } + else + { + GenTreeStmt* stmtPrev = stmtBefore->getPrevStmt(); + stmt->gtPrev = stmtPrev; + stmtPrev->gtNext = stmt; + } + stmt->gtNext = stmtBefore; + stmtBefore->gtPrev = stmt; } /***************************************************************************** @@ -1476,7 +1488,19 @@ GenTree* Compiler::impGetStructAddr(GenTree* structVal, // for Op2, but that would be out of order with op1, so we need to // spill op1 onto the statement list after whatever was last // before we recursed on Op2 (i.e. before whatever Op2 appended). - impInsertTreeBefore(structVal->gtOp.gtOp1, impCurStmtOffs, oldLastStmt->getNextStmt()); + GenTreeStmt* beforeStmt; + if (oldLastStmt == nullptr) + { + // The op1 stmt should be the first in the list. + beforeStmt = impStmtList; + } + else + { + // Insert after the oldLastStmt before the first inserted for op2. + beforeStmt = oldLastStmt->getNextStmt(); + } + + impInsertTreeBefore(structVal->gtOp.gtOp1, impCurStmtOffs, beforeStmt); structVal->gtOp.gtOp1 = gtNewNothingNode(); } From 83d07828c04758a3332f611cf54b9b9b5c371fb2 Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Fri, 19 Apr 2019 15:42:23 -0700 Subject: [PATCH 07/24] Add a repro test. --- .../JitBlue/GitHub_24114/GitHub_24114.cs | 34 +++++++++++++++++++ .../JitBlue/GitHub_24114/GitHub_24114.csproj | 33 ++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/src/JIT/Regression/JitBlue/GitHub_24114/GitHub_24114.cs create mode 100644 tests/src/JIT/Regression/JitBlue/GitHub_24114/GitHub_24114.csproj diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_24114/GitHub_24114.cs b/tests/src/JIT/Regression/JitBlue/GitHub_24114/GitHub_24114.cs new file mode 100644 index 000000000000..67a487a314be --- /dev/null +++ b/tests/src/JIT/Regression/JitBlue/GitHub_24114/GitHub_24114.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; + + +// The jit should correctly import get struct address as a first statement during the importation phase. + +namespace GitHub_24114 +{ + class Program + { + static int Main(string[] args) + { + Test(); + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Test() + { + var options = SimpleStruct.Default; + } + } + + public struct SimpleStruct + { + public static readonly SimpleStruct Default = new SimpleStruct() + { + }; + } +} diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_24114/GitHub_24114.csproj b/tests/src/JIT/Regression/JitBlue/GitHub_24114/GitHub_24114.csproj new file mode 100644 index 000000000000..95052d9884f1 --- /dev/null +++ b/tests/src/JIT/Regression/JitBlue/GitHub_24114/GitHub_24114.csproj @@ -0,0 +1,33 @@ + + + + + Debug + AnyCPU + 2.0 + {95DFC527-4DC1-495E-97D7-E94EE1F7140D} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + + + + + + + False + + + + None + True + + + + + + + + + + From 0410c3e4fe54590eea34aead28f550539d814b98 Mon Sep 17 00:00:00 2001 From: Koundinya Veluri Date: Fri, 19 Apr 2019 19:22:53 -0700 Subject: [PATCH 08/24] Implement APIs for some threading metrics (CoreCLR) (#24113) Implement APIs for some threading metrics (CoreCLR) API review: https://github.com/dotnet/corefx/issues/35500 --- .../shared/System/Threading/ThreadPool.cs | 53 +++++++++ .../src/System/Threading/Monitor.cs | 9 ++ .../System/Threading/ThreadPool.CoreCLR.cs | 30 +++++ src/classlibnative/bcltype/objectnative.cpp | 6 + src/classlibnative/bcltype/objectnative.h | 1 + src/vm/comthreadpool.cpp | 24 ++++ src/vm/comthreadpool.h | 3 + src/vm/ecalllist.h | 4 + src/vm/syncblk.cpp | 1 + src/vm/threadpoolrequest.h | 6 + src/vm/threads.cpp | 82 +++++++++---- src/vm/threads.h | 112 ++++++++++++++++-- src/vm/win32threadpool.cpp | 24 +++- src/vm/win32threadpool.h | 4 +- 14 files changed, 325 insertions(+), 34 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs b/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs index 19c043da5ad6..fc53fc158fe8 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/ThreadPool.cs @@ -381,6 +381,26 @@ public bool LocalFindAndPop(object obj) return null; } } + + public int Count + { + get + { + bool lockTaken = false; + try + { + m_foreignLock.Enter(ref lockTaken); + return Math.Max(0, m_tailIndex - m_headIndex); + } + finally + { + if (lockTaken) + { + m_foreignLock.Exit(useMemoryBarrier: false); + } + } + } + } } internal bool loggingEnabled; @@ -512,6 +532,21 @@ internal bool LocalFindAndPop(object callback) return callback; } + public long LocalCount + { + get + { + long count = 0; + foreach (WorkStealingQueue workStealingQueue in WorkStealingQueueList.Queues) + { + count += workStealingQueue.Count; + } + return count; + } + } + + public long GlobalCount => workItems.Count; + /// /// Dispatches work items to this thread. /// @@ -1241,5 +1276,23 @@ internal static object[] GetGloballyQueuedWorkItemsForDebugger() => internal static object[] GetLocallyQueuedWorkItemsForDebugger() => ToObjectArray(GetLocallyQueuedWorkItems()); + + /// + /// Gets the number of work items that are currently queued to be processed. + /// + /// + /// For a thread pool implementation that may have different types of work items, the count includes all types that can + /// be tracked, which may only be the user work items including tasks. Some implementations may also include queued + /// timer and wait callbacks in the count. On Windows, the count is unlikely to include the number of pending IO + /// completions, as they get posted directly to an IO completion port. + /// + public static long PendingWorkItemCount + { + get + { + ThreadPoolWorkQueue workQueue = ThreadPoolGlobals.workQueue; + return workQueue.LocalCount + workQueue.GlobalCount + PendingUnmanagedWorkItemCount; + } + } } } diff --git a/src/System.Private.CoreLib/src/System/Threading/Monitor.cs b/src/System.Private.CoreLib/src/System/Threading/Monitor.cs index 2701b3b555d9..cb3a0f61d514 100644 --- a/src/System.Private.CoreLib/src/System/Threading/Monitor.cs +++ b/src/System.Private.CoreLib/src/System/Threading/Monitor.cs @@ -233,5 +233,14 @@ public static void PulseAll(object obj) ObjPulseAll(obj); } + + /// + /// Gets the number of times there was contention upon trying to take a 's lock so far. + /// + public static extern long LockContentionCount + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } } } diff --git a/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs b/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs index 89030787ed48..76eb2db0adad 100644 --- a/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs +++ b/src/System.Private.CoreLib/src/System/Threading/ThreadPool.CoreCLR.cs @@ -231,6 +231,36 @@ public static void GetAvailableThreads(out int workerThreads, out int completion GetAvailableThreadsNative(out workerThreads, out completionPortThreads); } + /// + /// Gets the number of thread pool threads that currently exist. + /// + /// + /// For a thread pool implementation that may have different types of threads, the count includes all types. + /// + public static extern int ThreadCount + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + + /// + /// Gets the number of work items that have been processed so far. + /// + /// + /// For a thread pool implementation that may have different types of work items, the count includes all types. + /// + public static extern long CompletedWorkItemCount + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + + private static extern long PendingUnmanagedWorkItemCount + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + private static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException WaitHandle waitObject, WaitOrTimerCallback callBack, diff --git a/src/classlibnative/bcltype/objectnative.cpp b/src/classlibnative/bcltype/objectnative.cpp index 5304e0110e7d..a5619e107f79 100644 --- a/src/classlibnative/bcltype/objectnative.cpp +++ b/src/classlibnative/bcltype/objectnative.cpp @@ -355,3 +355,9 @@ FCIMPL1(FC_BOOL_RET, ObjectNative::IsLockHeld, Object* pThisUNSAFE) } FCIMPLEND +FCIMPL0(INT64, ObjectNative::GetMonitorLockContentionCount) +{ + FCALL_CONTRACT; + return (INT64)Thread::GetTotalMonitorLockContentionCount(); +} +FCIMPLEND diff --git a/src/classlibnative/bcltype/objectnative.h b/src/classlibnative/bcltype/objectnative.h index 573b04ea0a3a..3d008d95a907 100644 --- a/src/classlibnative/bcltype/objectnative.h +++ b/src/classlibnative/bcltype/objectnative.h @@ -38,6 +38,7 @@ class ObjectNative static FCDECL1(void, Pulse, Object* pThisUNSAFE); static FCDECL1(void, PulseAll, Object* pThisUNSAFE); static FCDECL1(FC_BOOL_RET, IsLockHeld, Object* pThisUNSAFE); + static FCDECL0(INT64, GetMonitorLockContentionCount); }; #endif // _OBJECTNATIVE_H_ diff --git a/src/vm/comthreadpool.cpp b/src/vm/comthreadpool.cpp index 18db9951cb0b..f5dc4233e029 100644 --- a/src/vm/comthreadpool.cpp +++ b/src/vm/comthreadpool.cpp @@ -181,6 +181,30 @@ FCIMPL2(VOID, ThreadPoolNative::CorGetAvailableThreads,DWORD* workerThreads, DWO } FCIMPLEND +/*****************************************************************************************************/ +FCIMPL0(INT32, ThreadPoolNative::GetThreadCount) +{ + FCALL_CONTRACT; + return ThreadpoolMgr::GetThreadCount(); +} +FCIMPLEND + +/*****************************************************************************************************/ +FCIMPL0(INT64, ThreadPoolNative::GetCompletedWorkItemCount) +{ + FCALL_CONTRACT; + return (INT64)Thread::GetTotalThreadPoolCompletionCount(); +} +FCIMPLEND + +/*****************************************************************************************************/ +FCIMPL0(INT64, ThreadPoolNative::GetPendingUnmanagedWorkItemCount) +{ + FCALL_CONTRACT; + return PerAppDomainTPCountList::GetUnmanagedTPCount()->GetNumRequests(); +} +FCIMPLEND + /*****************************************************************************************************/ FCIMPL0(VOID, ThreadPoolNative::NotifyRequestProgress) diff --git a/src/vm/comthreadpool.h b/src/vm/comthreadpool.h index 6b0f4ec969ca..d830c9e50513 100644 --- a/src/vm/comthreadpool.h +++ b/src/vm/comthreadpool.h @@ -28,6 +28,9 @@ class ThreadPoolNative static FCDECL2(FC_BOOL_RET, CorSetMinThreads, DWORD workerThreads, DWORD completionPortThreads); static FCDECL2(VOID, CorGetMinThreads, DWORD* workerThreads, DWORD* completionPortThreads); static FCDECL2(VOID, CorGetAvailableThreads, DWORD* workerThreads, DWORD* completionPortThreads); + static FCDECL0(INT32, GetThreadCount); + static FCDECL0(INT64, GetCompletedWorkItemCount); + static FCDECL0(INT64, GetPendingUnmanagedWorkItemCount); static FCDECL0(VOID, NotifyRequestProgress); static FCDECL0(FC_BOOL_RET, NotifyRequestComplete); diff --git a/src/vm/ecalllist.h b/src/vm/ecalllist.h index 50071ed410b1..4d9de5b4b989 100644 --- a/src/vm/ecalllist.h +++ b/src/vm/ecalllist.h @@ -678,6 +678,9 @@ FCFuncStart(gThreadPoolFuncs) FCFuncElement("GetAvailableThreadsNative", ThreadPoolNative::CorGetAvailableThreads) FCFuncElement("SetMinThreadsNative", ThreadPoolNative::CorSetMinThreads) FCFuncElement("GetMinThreadsNative", ThreadPoolNative::CorGetMinThreads) + FCFuncElement("get_ThreadCount", ThreadPoolNative::GetThreadCount) + FCFuncElement("get_CompletedWorkItemCount", ThreadPoolNative::GetCompletedWorkItemCount) + FCFuncElement("get_PendingUnmanagedWorkItemCount", ThreadPoolNative::GetPendingUnmanagedWorkItemCount) FCFuncElement("RegisterWaitForSingleObjectNative", ThreadPoolNative::CorRegisterWaitForSingleObject) FCFuncElement("BindIOCompletionCallbackNative", ThreadPoolNative::CorBindIoCompletionCallback) FCFuncElement("SetMaxThreadsNative", ThreadPoolNative::CorSetMaxThreads) @@ -915,6 +918,7 @@ FCFuncStart(gMonitorFuncs) FCFuncElement("ObjPulse", ObjectNative::Pulse) FCFuncElement("ObjPulseAll", ObjectNative::PulseAll) FCFuncElement("IsEnteredNative", ObjectNative::IsLockHeld) + FCFuncElement("get_LockContentionCount", ObjectNative::GetMonitorLockContentionCount) FCFuncEnd() FCFuncStart(gOverlappedFuncs) diff --git a/src/vm/syncblk.cpp b/src/vm/syncblk.cpp index ce31ac90b38b..9aea18f0c6c0 100644 --- a/src/vm/syncblk.cpp +++ b/src/vm/syncblk.cpp @@ -2575,6 +2575,7 @@ BOOL AwareLock::EnterEpilogHelper(Thread* pCurThread, INT32 timeOut) FireEtwContentionStart_V1(ETW::ContentionLog::ContentionStructs::ManagedContention, GetClrInstanceId()); LogContention(); + Thread::IncrementMonitorLockContentionCount(pCurThread); OBJECTREF obj = GetOwningObject(); diff --git a/src/vm/threadpoolrequest.h b/src/vm/threadpoolrequest.h index eaba82389cee..3b2da28b5663 100644 --- a/src/vm/threadpoolrequest.h +++ b/src/vm/threadpoolrequest.h @@ -221,6 +221,12 @@ class UnManagedPerAppDomainTPCount : public IPerAppDomainTPCount { _ASSERT(FALSE); } + inline ULONG GetNumRequests() + { + LIMITED_METHOD_CONTRACT; + return VolatileLoad(&m_NumRequests); + } + private: SpinLock m_lock; ULONG m_NumRequests; diff --git a/src/vm/threads.cpp b/src/vm/threads.cpp index 70c26118ea15..37d30c392ac4 100644 --- a/src/vm/threads.cpp +++ b/src/vm/threads.cpp @@ -83,7 +83,9 @@ PTR_ThreadLocalModule ThreadLocalBlock::GetTLMIfExists(MethodTable* pMT) BOOL Thread::s_fCleanFinalizedThread = FALSE; -Volatile Thread::s_threadPoolCompletionCountOverflow = 0; +UINT64 Thread::s_workerThreadPoolCompletionCountOverflow = 0; +UINT64 Thread::s_ioThreadPoolCompletionCountOverflow = 0; +UINT64 Thread::s_monitorLockContentionCountOverflow = 0; CrstStatic g_DeadlockAwareCrst; @@ -1528,7 +1530,9 @@ Thread::Thread() m_sfEstablisherOfActualHandlerFrame.Clear(); #endif // WIN64EXCEPTIONS - m_threadPoolCompletionCount = 0; + m_workerThreadPoolCompletionCount = 0; + m_ioThreadPoolCompletionCount = 0; + m_monitorLockContentionCount = 0; Thread *pThread = GetThread(); InitContext(); @@ -5350,9 +5354,15 @@ BOOL ThreadStore::RemoveThread(Thread *target) if (target->IsBackground()) s_pThreadStore->m_BackgroundThreadCount--; - FastInterlockExchangeAdd( - &Thread::s_threadPoolCompletionCountOverflow, - target->m_threadPoolCompletionCount); + FastInterlockExchangeAddLong( + (LONGLONG *)&Thread::s_workerThreadPoolCompletionCountOverflow, + target->m_workerThreadPoolCompletionCount); + FastInterlockExchangeAddLong( + (LONGLONG *)&Thread::s_ioThreadPoolCompletionCountOverflow, + target->m_ioThreadPoolCompletionCount); + FastInterlockExchangeAddLong( + (LONGLONG *)&Thread::s_monitorLockContentionCountOverflow, + target->m_monitorLockContentionCount); _ASSERTE(s_pThreadStore->m_ThreadCount >= 0); _ASSERTE(s_pThreadStore->m_BackgroundThreadCount >= 0); @@ -8002,7 +8012,23 @@ BOOL ThreadStore::HoldingThreadStore(Thread *pThread) } } -LONG Thread::GetTotalThreadPoolCompletionCount() +NOINLINE void Thread::OnIncrementCountOverflow(UINT32 *threadLocalCount, UINT64 *overflowCount) +{ + WRAPPER_NO_CONTRACT; + _ASSERTE(threadLocalCount != nullptr); + _ASSERTE(overflowCount != nullptr); + + // Increment overflow, accumulate the count for this increment into the overflow count and reset the thread-local count + + // The thread store lock, in coordination with other places that read these values, ensures that both changes + // below become visible together + ThreadStoreLockHolder tsl; + + *threadLocalCount = 0; + InterlockedExchangeAdd64((LONGLONG *)overflowCount, (LONGLONG)UINT32_MAX + 1); +} + +UINT64 Thread::GetTotalCount(SIZE_T threadLocalCountOffset, UINT64 *overflowCount) { CONTRACTL { @@ -8011,32 +8037,44 @@ LONG Thread::GetTotalThreadPoolCompletionCount() } CONTRACTL_END; - LONG total; - if (g_fEEStarted) //make sure we actually have a thread store - { - // make sure up-to-date thread-local counts are visible to us - ::FlushProcessWriteBuffers(); + // enumerate all threads, summing their local counts. + ThreadStoreLockHolder tsl; - // enumerate all threads, summing their local counts. - ThreadStoreLockHolder tsl; + UINT64 total = GetOverflowCount(overflowCount); - total = s_threadPoolCompletionCountOverflow.Load(); + Thread *pThread = NULL; + while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL) + { + total += *GetThreadLocalCountRef(pThread, threadLocalCountOffset); + } - Thread *pThread = NULL; - while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL) - { - total += pThread->m_threadPoolCompletionCount; - } + return total; +} + +UINT64 Thread::GetTotalThreadPoolCompletionCount() +{ + CONTRACTL + { + NOTHROW; + MODE_ANY; } - else + CONTRACTL_END; + + // enumerate all threads, summing their local counts. + ThreadStoreLockHolder tsl; + + UINT64 total = GetWorkerThreadPoolCompletionCountOverflow() + GetIOThreadPoolCompletionCountOverflow(); + + Thread *pThread = NULL; + while ((pThread = ThreadStore::GetAllThreadList(pThread, 0, 0)) != NULL) { - total = s_threadPoolCompletionCountOverflow.Load(); + total += pThread->m_workerThreadPoolCompletionCount; + total += pThread->m_ioThreadPoolCompletionCount; } return total; } - INT32 Thread::ResetManagedThreadObject(INT32 nPriority) { CONTRACTL { diff --git a/src/vm/threads.h b/src/vm/threads.h index e5307d9a5dd5..ee3e69f4f231 100644 --- a/src/vm/threads.h +++ b/src/vm/threads.h @@ -3871,21 +3871,113 @@ class Thread: public IUnknown #endif // defined(FEATURE_PROFAPI_ATTACH_DETACH) || defined(DATA_PROFAPI_ATTACH_DETACH) private: - Volatile m_threadPoolCompletionCount; - static Volatile s_threadPoolCompletionCountOverflow; //counts completions for threads that have been destroyed. + UINT32 m_workerThreadPoolCompletionCount; + static UINT64 s_workerThreadPoolCompletionCountOverflow; + UINT32 m_ioThreadPoolCompletionCount; + static UINT64 s_ioThreadPoolCompletionCountOverflow; + UINT32 m_monitorLockContentionCount; + static UINT64 s_monitorLockContentionCountOverflow; -public: - static void IncrementThreadPoolCompletionCount() +#ifndef DACCESS_COMPILE +private: + static UINT32 *GetThreadLocalCountRef(Thread *pThread, SIZE_T threadLocalCountOffset) { - LIMITED_METHOD_CONTRACT; - Thread* pThread = GetThread(); - if (pThread) - pThread->m_threadPoolCompletionCount++; + WRAPPER_NO_CONTRACT; + _ASSERTE(threadLocalCountOffset <= sizeof(Thread) - sizeof(UINT32)); + + return (UINT32 *)((SIZE_T)pThread + threadLocalCountOffset); + } + + static void IncrementCount(Thread *pThread, SIZE_T threadLocalCountOffset, UINT64 *overflowCount) + { + WRAPPER_NO_CONTRACT; + _ASSERTE(overflowCount != nullptr); + + if (pThread != nullptr) + { + UINT32 *threadLocalCount = GetThreadLocalCountRef(pThread, threadLocalCountOffset); + UINT32 newCount = *threadLocalCount + 1; + if (newCount != 0) + { + VolatileStoreWithoutBarrier(threadLocalCount, newCount); + } + else + { + OnIncrementCountOverflow(threadLocalCount, overflowCount); + } + } else - FastInterlockIncrement(&s_threadPoolCompletionCountOverflow); + { + InterlockedIncrement64((LONGLONG *)overflowCount); + } + } + + static void OnIncrementCountOverflow(UINT32 *threadLocalCount, UINT64 *overflowCount); + + static UINT64 GetOverflowCount(UINT64 *overflowCount) + { + WRAPPER_NO_CONTRACT; + + if (sizeof(void *) >= sizeof(*overflowCount)) + { + return VolatileLoad(overflowCount); + } + return InterlockedCompareExchange64((LONGLONG *)overflowCount, 0, 0); // prevent tearing + } + + static UINT64 GetTotalCount(SIZE_T threadLocalCountOffset, UINT64 *overflowCount); + +public: + static void IncrementWorkerThreadPoolCompletionCount(Thread *pThread) + { + WRAPPER_NO_CONTRACT; + IncrementCount(pThread, offsetof(Thread, m_workerThreadPoolCompletionCount), &s_workerThreadPoolCompletionCountOverflow); + } + + static UINT64 GetWorkerThreadPoolCompletionCountOverflow() + { + WRAPPER_NO_CONTRACT; + return GetOverflowCount(&s_workerThreadPoolCompletionCountOverflow); + } + + static UINT64 GetTotalWorkerThreadPoolCompletionCount() + { + WRAPPER_NO_CONTRACT; + return GetTotalCount(offsetof(Thread, m_workerThreadPoolCompletionCount), &s_workerThreadPoolCompletionCountOverflow); + } + + static void IncrementIOThreadPoolCompletionCount(Thread *pThread) + { + WRAPPER_NO_CONTRACT; + IncrementCount(pThread, offsetof(Thread, m_ioThreadPoolCompletionCount), &s_ioThreadPoolCompletionCountOverflow); + } + + static UINT64 GetIOThreadPoolCompletionCountOverflow() + { + WRAPPER_NO_CONTRACT; + return GetOverflowCount(&s_ioThreadPoolCompletionCountOverflow); + } + + static UINT64 GetTotalThreadPoolCompletionCount(); + + static void IncrementMonitorLockContentionCount(Thread *pThread) + { + WRAPPER_NO_CONTRACT; + IncrementCount(pThread, offsetof(Thread, m_monitorLockContentionCount), &s_monitorLockContentionCountOverflow); } - static LONG GetTotalThreadPoolCompletionCount(); + static UINT64 GetMonitorLockContentionCountOverflow() + { + WRAPPER_NO_CONTRACT; + return GetOverflowCount(&s_monitorLockContentionCountOverflow); + } + + static UINT64 GetTotalMonitorLockContentionCount() + { + WRAPPER_NO_CONTRACT; + return GetTotalCount(offsetof(Thread, m_monitorLockContentionCount), &s_monitorLockContentionCountOverflow); + } +#endif // !DACCESS_COMPILE private: diff --git a/src/vm/win32threadpool.cpp b/src/vm/win32threadpool.cpp index f340542b0c93..da44beb72e92 100644 --- a/src/vm/win32threadpool.cpp +++ b/src/vm/win32threadpool.cpp @@ -693,6 +693,18 @@ BOOL ThreadpoolMgr::GetAvailableThreads(DWORD* AvailableWorkerThreads, return TRUE; } +INT32 ThreadpoolMgr::GetThreadCount() +{ + WRAPPER_NO_CONTRACT; + + if (!IsInitialized()) + { + return 0; + } + + return WorkerCounter.DangerousGetDirtyCounts().NumActive + CPThreadCounter.DangerousGetDirtyCounts().NumActive; +} + void QueueUserWorkItemHelp(LPTHREAD_START_ROUTINE Function, PVOID Context) { STATIC_CONTRACT_THROWS; @@ -911,7 +923,7 @@ void ThreadpoolMgr::AdjustMaxWorkersActive() _ASSERTE(ThreadAdjustmentLock.IsHeld()); DWORD currentTicks = GetTickCount(); - LONG totalNumCompletions = Thread::GetTotalThreadPoolCompletionCount(); + LONG totalNumCompletions = (LONG)Thread::GetTotalWorkerThreadPoolCompletionCount(); LONG numCompletions = totalNumCompletions - VolatileLoad(&PriorCompletedWorkRequests); LARGE_INTEGER startTime = CurrentSampleStartTime; @@ -2864,6 +2876,10 @@ DWORD WINAPI ThreadpoolMgr::AsyncCallbackCompletion(PVOID pArgs) ((WAITORTIMERCALLBACKFUNC) waitInfo->Callback) ( waitInfo->Context, asyncCallback->waitTimedOut != FALSE); + +#ifndef FEATURE_PAL + Thread::IncrementIOThreadPoolCompletionCount(pThread); +#endif } return ERROR_SUCCESS; @@ -3604,6 +3620,12 @@ DWORD WINAPI ThreadpoolMgr::CompletionPortThreadStart(LPVOID lpArgs) ((LPOVERLAPPED_COMPLETION_ROUTINE) key)(errorCode, numBytes, pOverlapped); } + if ((void *)key != CallbackForInitiateDrainageOfCompletionPortQueue && + (void *)key != CallbackForContinueDrainageOfCompletionPortQueue) + { + Thread::IncrementIOThreadPoolCompletionCount(pThread); + } + if (pThread == NULL) { pThread = GetThread(); } diff --git a/src/vm/win32threadpool.h b/src/vm/win32threadpool.h index 55f321c37f50..ff47ea2007e0 100644 --- a/src/vm/win32threadpool.h +++ b/src/vm/win32threadpool.h @@ -244,6 +244,8 @@ class ThreadpoolMgr static BOOL GetAvailableThreads(DWORD* AvailableWorkerThreads, DWORD* AvailableIOCompletionThreads); + static INT32 GetThreadCount(); + static BOOL QueueUserWorkItem(LPTHREAD_START_ROUTINE Function, PVOID Context, ULONG Flags, @@ -841,7 +843,7 @@ class ThreadpoolMgr static void NotifyWorkItemCompleted() { WRAPPER_NO_CONTRACT; - Thread::IncrementThreadPoolCompletionCount(); + Thread::IncrementWorkerThreadPoolCompletionCount(GetThread()); UpdateLastDequeueTime(); } From 0f93182d5d8eeefe18ec50b1d4d32fd5d05333eb Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 19 Apr 2019 20:33:46 -0700 Subject: [PATCH 09/24] Pulling in CoreFX changes and fixing up Sse2.StoreLow to be Sse2.StoreScalar (#24123) * Update dependencies from https://github.com/dotnet/corefx build 20190419.5 - Microsoft.NETCore.Platforms - 3.0.0-preview5.19219.5 - Microsoft.Private.CoreFx.NETCoreApp - 4.6.0-preview5.19219.5 * Removing the Sse2.StoreLow(long/ulong*, ...) methods that have been replaced * Moving the Sse2.StoreLow(long/ulong*, ...) tests to be Sse2.StoreScalar tests * Fixing some existing usages of Sse2.StoreLow(long/ulong*, ...) --- eng/Version.Details.xml | 8 ++--- eng/Versions.props | 4 +-- .../X86/Sse2.PlatformNotSupported.cs | 10 ------ .../System/Runtime/Intrinsics/X86/Sse2.cs | 10 ------ .../shared/System/Text/ASCIIUtility.cs | 6 ++-- src/jit/hwintrinsiclistxarch.h | 2 +- .../HardwareIntrinsics/X86/Sse2/StoreLow.cs | 34 ------------------- .../X86/Sse2/StoreScalar.cs | 34 +++++++++++++++++++ 8 files changed, 44 insertions(+), 64 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 557bb50f8f9f..75b470254ed4 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -11,13 +11,13 @@ https://github.com/dotnet/arcade 517bf671ea342965d007aa48f5bfd4926e58d582 - + https://github.com/dotnet/corefx - 0a9a366e290aded3e9bf5d082d6beee5ff560177 + 069f540c174aab3d2c0501d339ab49c14a2b3c19 - + https://github.com/dotnet/corefx - 0a9a366e290aded3e9bf5d082d6beee5ff560177 + 069f540c174aab3d2c0501d339ab49c14a2b3c19 https://github.com/dotnet/core-setup diff --git a/eng/Versions.props b/eng/Versions.props index 11412f26ab02..5f8b13d2d61b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -9,8 +9,8 @@ true 2.5.1-beta.19179.4 1.0.0-alpha-004 - 4.6.0-preview5.19218.11 - 3.0.0-preview5.19218.11 + 4.6.0-preview5.19219.5 + 3.0.0-preview5.19219.5 3.0.0-preview5-27618-16 99.99.99-master-20190313.3 99.99.99-master-20190313.3 diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Intrinsics/X86/Sse2.PlatformNotSupported.cs b/src/System.Private.CoreLib/shared/System/Runtime/Intrinsics/X86/Sse2.PlatformNotSupported.cs index b787cc916dd4..57b3195e3f1b 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/Intrinsics/X86/Sse2.PlatformNotSupported.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/Intrinsics/X86/Sse2.PlatformNotSupported.cs @@ -1450,16 +1450,6 @@ internal X64() { } /// public static unsafe void StoreHigh(double* address, Vector128 source) { throw new PlatformNotSupportedException(); } - /// - /// void _mm_storel_epi64 (__m128i* mem_addr, __m128i a) - /// MOVQ m64, xmm - /// - public static unsafe void StoreLow(long* address, Vector128 source) { throw new PlatformNotSupportedException(); } - /// - /// void _mm_storel_epi64 (__m128i* mem_addr, __m128i a) - /// MOVQ m64, xmm - /// - public static unsafe void StoreLow(ulong* address, Vector128 source) { throw new PlatformNotSupportedException(); } /// /// void _mm_storel_pd (double* mem_addr, __m128d a) /// MOVLPD m64, xmm diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Intrinsics/X86/Sse2.cs b/src/System.Private.CoreLib/shared/System/Runtime/Intrinsics/X86/Sse2.cs index ad674457a9a7..3aff2b4d53a8 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/Intrinsics/X86/Sse2.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/Intrinsics/X86/Sse2.cs @@ -1456,16 +1456,6 @@ internal X64() { } /// public static unsafe void StoreHigh(double* address, Vector128 source) => StoreHigh(address, source); - /// - /// void _mm_storel_epi64 (__m128i* mem_addr, __m128i a) - /// MOVQ m64, xmm - /// - public static unsafe void StoreLow(long* address, Vector128 source) => StoreLow(address, source); - /// - /// void _mm_storel_epi64 (__m128i* mem_addr, __m128i a) - /// MOVQ m64, xmm - /// - public static unsafe void StoreLow(ulong* address, Vector128 source) => StoreLow(address, source); /// /// void _mm_storel_pd (double* mem_addr, __m128d a) /// MOVLPD m64, xmm diff --git a/src/System.Private.CoreLib/shared/System/Text/ASCIIUtility.cs b/src/System.Private.CoreLib/shared/System/Text/ASCIIUtility.cs index 87b742f678ad..31e8ac1d5726 100644 --- a/src/System.Private.CoreLib/shared/System/Text/ASCIIUtility.cs +++ b/src/System.Private.CoreLib/shared/System/Text/ASCIIUtility.cs @@ -1347,7 +1347,7 @@ private static unsafe nuint NarrowUtf16ToAscii_Sse2(char* pUtf16Buffer, byte* pA // Turn the 8 ASCII chars we just read into 8 ASCII bytes, then copy it to the destination. Vector128 asciiVector = Sse2.PackUnsignedSaturate(utf16VectorFirst, utf16VectorFirst); - Sse2.StoreLow((ulong*)pAsciiBuffer, asciiVector.AsUInt64()); // ulong* calculated here is UNALIGNED + Sse2.StoreScalar((ulong*)pAsciiBuffer, asciiVector.AsUInt64()); // ulong* calculated here is UNALIGNED nuint currentOffsetInElements = SizeOfVector128 / 2; // we processed 8 elements so far @@ -1386,7 +1386,7 @@ private static unsafe nuint NarrowUtf16ToAscii_Sse2(char* pUtf16Buffer, byte* pA // Turn the 8 ASCII chars we just read into 8 ASCII bytes, then copy it to the destination. asciiVector = Sse2.PackUnsignedSaturate(utf16VectorFirst, utf16VectorFirst); - Sse2.StoreLow((ulong*)(pAsciiBuffer + currentOffsetInElements), asciiVector.AsUInt64()); // ulong* calculated here is UNALIGNED + Sse2.StoreScalar((ulong*)(pAsciiBuffer + currentOffsetInElements), asciiVector.AsUInt64()); // ulong* calculated here is UNALIGNED } // Calculate how many elements we wrote in order to get pAsciiBuffer to its next alignment @@ -1462,7 +1462,7 @@ private static unsafe nuint NarrowUtf16ToAscii_Sse2(char* pUtf16Buffer, byte* pA Debug.Assert(((nuint)pAsciiBuffer + currentOffsetInElements) % sizeof(ulong) == 0, "Destination should be ulong-aligned."); - Sse2.StoreLow((ulong*)(pAsciiBuffer + currentOffsetInElements), asciiVector.AsUInt64()); // ulong* calculated here is aligned + Sse2.StoreScalar((ulong*)(pAsciiBuffer + currentOffsetInElements), asciiVector.AsUInt64()); // ulong* calculated here is aligned currentOffsetInElements += SizeOfVector128 / 2; goto Finish; diff --git a/src/jit/hwintrinsiclistxarch.h b/src/jit/hwintrinsiclistxarch.h index 433744ca31c6..41ddfc8dd238 100644 --- a/src/jit/hwintrinsiclistxarch.h +++ b/src/jit/hwintrinsiclistxarch.h @@ -267,7 +267,7 @@ HARDWARE_INTRINSIC(SSE2_Store, "Store", HARDWARE_INTRINSIC(SSE2_StoreAligned, "StoreAligned", SSE2, -1, 16, 2, {INS_movdqa, INS_movdqa, INS_movdqa, INS_movdqa, INS_movdqa, INS_movdqa, INS_movdqa, INS_movdqa, INS_invalid, INS_movapd}, HW_Category_MemoryStore, HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(SSE2_StoreAlignedNonTemporal, "StoreAlignedNonTemporal", SSE2, -1, 16, 2, {INS_movntdq, INS_movntdq, INS_movntdq, INS_movntdq, INS_movntdq, INS_movntdq, INS_movntdq, INS_movntdq, INS_invalid, INS_movntpd}, HW_Category_MemoryStore, HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(SSE2_StoreHigh, "StoreHigh", SSE2, -1, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movhpd}, HW_Category_MemoryStore, HW_Flag_NoRMWSemantics) -HARDWARE_INTRINSIC(SSE2_StoreLow, "StoreLow", SSE2, -1, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movq, INS_movq, INS_invalid, INS_movlpd}, HW_Category_MemoryStore, HW_Flag_NoRMWSemantics) +HARDWARE_INTRINSIC(SSE2_StoreLow, "StoreLow", SSE2, -1, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movlpd}, HW_Category_MemoryStore, HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(SSE2_StoreNonTemporal, "StoreNonTemporal", SSE2, -1, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movnti, INS_movnti, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_MemoryStore, HW_Flag_NoRMWSemantics|HW_Flag_SpecialCodeGen) HARDWARE_INTRINSIC(SSE2_StoreScalar, "StoreScalar", SSE2, -1, 16, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movq, INS_movq, INS_invalid, INS_movsdsse2}, HW_Category_MemoryStore, HW_Flag_NoRMWSemantics) HARDWARE_INTRINSIC(SSE2_Subtract, "Subtract", SSE2, -1, 16, 2, {INS_psubb, INS_psubb, INS_psubw, INS_psubw, INS_psubd, INS_psubd, INS_psubq, INS_psubq, INS_invalid, INS_subpd}, HW_Category_SimpleSIMD, HW_Flag_NoFlag) diff --git a/tests/src/JIT/HardwareIntrinsics/X86/Sse2/StoreLow.cs b/tests/src/JIT/HardwareIntrinsics/X86/Sse2/StoreLow.cs index df156d7fe708..5bca6881d3f8 100644 --- a/tests/src/JIT/HardwareIntrinsics/X86/Sse2/StoreLow.cs +++ b/tests/src/JIT/HardwareIntrinsics/X86/Sse2/StoreLow.cs @@ -39,40 +39,6 @@ static unsafe int Main(string[] args) testResult = Fail; } } - - using (TestTable intTable = new TestTable(new long[2] { 1, -5 }, new long[2])) - { - var vf = Unsafe.Read>(intTable.inArrayPtr); - Sse2.StoreLow((long*)(intTable.outArrayPtr), vf); - - if (!intTable.CheckResult((x, y) => y[0] == x[0] && y[1] == 0)) - { - Console.WriteLine("Sse2 StoreLow failed on long:"); - foreach (var item in intTable.outArray) - { - Console.Write(item + ", "); - } - Console.WriteLine(); - testResult = Fail; - } - } - - using (TestTable intTable = new TestTable(new ulong[2] { 1, 5 }, new ulong[2])) - { - var vf = Unsafe.Read>(intTable.inArrayPtr); - Sse2.StoreLow((ulong*)(intTable.outArrayPtr), vf); - - if (!intTable.CheckResult((x, y) => y[0] == x[0] && y[1] == 0)) - { - Console.WriteLine("Sse2 StoreLow failed on ulong:"); - foreach (var item in intTable.outArray) - { - Console.Write(item + ", "); - } - Console.WriteLine(); - testResult = Fail; - } - } } return testResult; diff --git a/tests/src/JIT/HardwareIntrinsics/X86/Sse2/StoreScalar.cs b/tests/src/JIT/HardwareIntrinsics/X86/Sse2/StoreScalar.cs index 51d29a1fc27e..9b8f2b43b474 100644 --- a/tests/src/JIT/HardwareIntrinsics/X86/Sse2/StoreScalar.cs +++ b/tests/src/JIT/HardwareIntrinsics/X86/Sse2/StoreScalar.cs @@ -39,6 +39,40 @@ static unsafe int Main(string[] args) testResult = Fail; } } + + using (TestTable intTable = new TestTable(new long[2] { 1, -5 }, new long[2])) + { + var vf = Unsafe.Read>(intTable.inArrayPtr); + Sse2.StoreScalar((long*)(intTable.outArrayPtr), vf); + + if (!intTable.CheckResult((x, y) => y[0] == x[0] && y[1] == 0)) + { + Console.WriteLine("Sse2 StoreScalar failed on long:"); + foreach (var item in intTable.outArray) + { + Console.Write(item + ", "); + } + Console.WriteLine(); + testResult = Fail; + } + } + + using (TestTable intTable = new TestTable(new ulong[2] { 1, 5 }, new ulong[2])) + { + var vf = Unsafe.Read>(intTable.inArrayPtr); + Sse2.StoreScalar((ulong*)(intTable.outArrayPtr), vf); + + if (!intTable.CheckResult((x, y) => y[0] == x[0] && y[1] == 0)) + { + Console.WriteLine("Sse2 StoreScalar failed on ulong:"); + foreach (var item in intTable.outArray) + { + Console.Write(item + ", "); + } + Console.WriteLine(); + testResult = Fail; + } + } } return testResult; From 324344f82a6f8221d28f9015f64be06779632468 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Fri, 19 Apr 2019 22:11:39 -0700 Subject: [PATCH 10/24] The thread triggering finalizer shutdown events should be in an alertable state. (#24133) --- src/vm/finalizerthread.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vm/finalizerthread.h b/src/vm/finalizerthread.h index d5063b21668e..3a62814d18e1 100644 --- a/src/vm/finalizerthread.h +++ b/src/vm/finalizerthread.h @@ -68,7 +68,11 @@ class FinalizerThread // Do not wait for FinalizerThread if the current one is FinalizerThread. if (GetThread() != GetFinalizerThread()) - hEventFinalizerToShutDown->Wait(INFINITE,FALSE); + { + // This wait must be alertable to handle cases where the current + // thread's context is needed (i.e. RCW cleanup) + hEventFinalizerToShutDown->Wait(INFINITE, /*alertable*/ TRUE); + } } static void FinalizerThreadWait(DWORD timeout = INFINITE); From bf0600c9ce4b9de06d80b7a02b33eb174b3dd64b Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Sat, 20 Apr 2019 02:07:42 -0400 Subject: [PATCH 11/24] Fix Timezone whitespace (#24137) --- .../shared/System/TimeZoneInfo.Win32.cs | 1972 ++++---- .../shared/System/TimeZoneInfo.cs | 4120 ++++++++--------- 2 files changed, 3046 insertions(+), 3046 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs index 416b2c81e7b8..8fe5f56fb0c0 100644 --- a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs +++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.Win32.cs @@ -1,986 +1,986 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Security; -using System.Text; -using System.Threading; - -using Microsoft.Win32.SafeHandles; - -using Internal.Win32; -using Internal.Runtime.CompilerServices; - -using REG_TZI_FORMAT = Interop.Kernel32.REG_TZI_FORMAT; -using TIME_ZONE_INFORMATION = Interop.Kernel32.TIME_ZONE_INFORMATION; -using TIME_DYNAMIC_ZONE_INFORMATION = Interop.Kernel32.TIME_DYNAMIC_ZONE_INFORMATION; - -namespace System -{ - public sealed partial class TimeZoneInfo - { - // registry constants for the 'Time Zones' hive - // - private const string TimeZonesRegistryHive = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"; - private const string DisplayValue = "Display"; - private const string DaylightValue = "Dlt"; - private const string StandardValue = "Std"; - private const string MuiDisplayValue = "MUI_Display"; - private const string MuiDaylightValue = "MUI_Dlt"; - private const string MuiStandardValue = "MUI_Std"; - private const string TimeZoneInfoValue = "TZI"; - private const string FirstEntryValue = "FirstEntry"; - private const string LastEntryValue = "LastEntry"; - - private const int MaxKeyLength = 255; - - private sealed partial class CachedData - { - private static TimeZoneInfo GetCurrentOneYearLocal() - { - // load the data from the OS - TIME_ZONE_INFORMATION timeZoneInformation; - uint result = Interop.Kernel32.GetTimeZoneInformation(out timeZoneInformation); - return result == Interop.Kernel32.TIME_ZONE_ID_INVALID ? - CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId) : - GetLocalTimeZoneFromWin32Data(timeZoneInformation, dstDisabled: false); - } - - private volatile OffsetAndRule? _oneYearLocalFromUtc; - - public OffsetAndRule GetOneYearLocalFromUtc(int year) - { - OffsetAndRule? oneYearLocFromUtc = _oneYearLocalFromUtc; - if (oneYearLocFromUtc == null || oneYearLocFromUtc.Year != year) - { - TimeZoneInfo currentYear = GetCurrentOneYearLocal(); - AdjustmentRule? rule = currentYear._adjustmentRules == null ? null : currentYear._adjustmentRules[0]; - oneYearLocFromUtc = new OffsetAndRule(year, currentYear.BaseUtcOffset, rule); - _oneYearLocalFromUtc = oneYearLocFromUtc; - } - return oneYearLocFromUtc; - } - } - - private sealed class OffsetAndRule - { - public readonly int Year; - public readonly TimeSpan Offset; - public readonly AdjustmentRule? Rule; - - public OffsetAndRule(int year, TimeSpan offset, AdjustmentRule? rule) - { - Year = year; - Offset = offset; - Rule = rule; - } - } - - /// - /// Returns a cloned array of AdjustmentRule objects - /// - public AdjustmentRule[] GetAdjustmentRules() - { - if (_adjustmentRules == null) - { - return Array.Empty(); - } - - return (AdjustmentRule[])_adjustmentRules.Clone(); - } - - private static void PopulateAllSystemTimeZones(CachedData cachedData) - { - Debug.Assert(Monitor.IsEntered(cachedData)); - - using (RegistryKey? reg = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive, writable: false)) - { - if (reg != null) - { - foreach (string keyName in reg.GetSubKeyNames()) - { - TryGetTimeZone(keyName, false, out _, out _, cachedData); // populate the cache - } - } - } - } - - private TimeZoneInfo(in TIME_ZONE_INFORMATION zone, bool dstDisabled) - { - string standardName = zone.GetStandardName(); - if (standardName.Length == 0) - { - _id = LocalId; // the ID must contain at least 1 character - initialize _id to "Local" - } - else - { - _id = standardName; - } - _baseUtcOffset = new TimeSpan(0, -(zone.Bias), 0); - - if (!dstDisabled) - { - // only create the adjustment rule if DST is enabled - REG_TZI_FORMAT regZone = new REG_TZI_FORMAT(zone); - AdjustmentRule? rule = CreateAdjustmentRuleFromTimeZoneInformation(regZone, DateTime.MinValue.Date, DateTime.MaxValue.Date, zone.Bias); - if (rule != null) - { - _adjustmentRules = new[] { rule }; - } - } - - ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime); - _displayName = standardName; - _standardDisplayName = standardName; - _daylightDisplayName = zone.GetDaylightName(); - } - - /// - /// Helper function to check if the current TimeZoneInformation struct does not support DST. - /// This check returns true when the DaylightDate == StandardDate. - /// This check is only meant to be used for "Local". - /// - private static bool CheckDaylightSavingTimeNotSupported(in TIME_ZONE_INFORMATION timeZone) => - timeZone.DaylightDate.Equals(timeZone.StandardDate); - - /// - /// Converts a REG_TZI_FORMAT struct to an AdjustmentRule. - /// - private static AdjustmentRule? CreateAdjustmentRuleFromTimeZoneInformation(in REG_TZI_FORMAT timeZoneInformation, DateTime startDate, DateTime endDate, int defaultBaseUtcOffset) - { - bool supportsDst = timeZoneInformation.StandardDate.Month != 0; - - if (!supportsDst) - { - if (timeZoneInformation.Bias == defaultBaseUtcOffset) - { - // this rule will not contain any information to be used to adjust dates. just ignore it - return null; - } - - return AdjustmentRule.CreateAdjustmentRule( - startDate, - endDate, - TimeSpan.Zero, // no daylight saving transition - TransitionTime.CreateFixedDateRule(DateTime.MinValue, 1, 1), - TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(1), 1, 1), - new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0), // Bias delta is all what we need from this rule - noDaylightTransitions: false); - } - - // - // Create an AdjustmentRule with TransitionTime objects - // - TransitionTime daylightTransitionStart; - if (!TransitionTimeFromTimeZoneInformation(timeZoneInformation, out daylightTransitionStart, readStartDate: true)) - { - return null; - } - - TransitionTime daylightTransitionEnd; - if (!TransitionTimeFromTimeZoneInformation(timeZoneInformation, out daylightTransitionEnd, readStartDate: false)) - { - return null; - } - - if (daylightTransitionStart.Equals(daylightTransitionEnd)) - { - // this happens when the time zone does support DST but the OS has DST disabled - return null; - } - - return AdjustmentRule.CreateAdjustmentRule( - startDate, - endDate, - new TimeSpan(0, -timeZoneInformation.DaylightBias, 0), - daylightTransitionStart, - daylightTransitionEnd, - new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0), - noDaylightTransitions: false); - } - - /// - /// Helper function that searches the registry for a time zone entry - /// that matches the TimeZoneInformation struct. - /// - private static string? FindIdFromTimeZoneInformation(in TIME_ZONE_INFORMATION timeZone, out bool dstDisabled) - { - dstDisabled = false; - - using (RegistryKey? key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive, writable: false)) - { - if (key == null) - { - return null; - } - - foreach (string keyName in key.GetSubKeyNames()) - { - if (TryCompareTimeZoneInformationToRegistry(timeZone, keyName, out dstDisabled)) - { - return keyName; - } - } - } - - return null; - } - - /// - /// Helper function for retrieving the local system time zone. - /// May throw COMException, TimeZoneNotFoundException, InvalidTimeZoneException. - /// Assumes cachedData lock is taken. - /// - /// A new TimeZoneInfo instance. - private static TimeZoneInfo GetLocalTimeZone(CachedData cachedData) - { - Debug.Assert(Monitor.IsEntered(cachedData)); - - // - // Try using the "kernel32!GetDynamicTimeZoneInformation" API to get the "id" - // - var dynamicTimeZoneInformation = new TIME_DYNAMIC_ZONE_INFORMATION(); - - // call kernel32!GetDynamicTimeZoneInformation... - uint result = Interop.Kernel32.GetDynamicTimeZoneInformation(out dynamicTimeZoneInformation); - if (result == Interop.Kernel32.TIME_ZONE_ID_INVALID) - { - // return a dummy entry - return CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId); - } - - // check to see if we can use the key name returned from the API call - string dynamicTimeZoneKeyName = dynamicTimeZoneInformation.GetTimeZoneKeyName(); - if (dynamicTimeZoneKeyName.Length != 0) - { - if (TryGetTimeZone(dynamicTimeZoneKeyName, dynamicTimeZoneInformation.DynamicDaylightTimeDisabled != 0, out TimeZoneInfo? zone, out _, cachedData) == TimeZoneInfoResult.Success) - { - // successfully loaded the time zone from the registry - return zone!; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 - } - } - - var timeZoneInformation = new TIME_ZONE_INFORMATION(dynamicTimeZoneInformation); - - // the key name was not returned or it pointed to a bogus entry - search for the entry ourselves - string? id = FindIdFromTimeZoneInformation(timeZoneInformation, out bool dstDisabled); - - if (id != null) - { - if (TryGetTimeZone(id, dstDisabled, out TimeZoneInfo? zone, out _, cachedData) == TimeZoneInfoResult.Success) - { - // successfully loaded the time zone from the registry - return zone!; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 - } - } - - // We could not find the data in the registry. Fall back to using - // the data from the Win32 API - return GetLocalTimeZoneFromWin32Data(timeZoneInformation, dstDisabled); - } - - /// - /// Helper function used by 'GetLocalTimeZone()' - this function wraps a bunch of - /// try/catch logic for handling the TimeZoneInfo private constructor that takes - /// a TIME_ZONE_INFORMATION structure. - /// - private static TimeZoneInfo GetLocalTimeZoneFromWin32Data(in TIME_ZONE_INFORMATION timeZoneInformation, bool dstDisabled) - { - // first try to create the TimeZoneInfo with the original 'dstDisabled' flag - try - { - return new TimeZoneInfo(timeZoneInformation, dstDisabled); - } - catch (ArgumentException) { } - catch (InvalidTimeZoneException) { } - - // if 'dstDisabled' was false then try passing in 'true' as a last ditch effort - if (!dstDisabled) - { - try - { - return new TimeZoneInfo(timeZoneInformation, dstDisabled: true); - } - catch (ArgumentException) { } - catch (InvalidTimeZoneException) { } - } - - // the data returned from Windows is completely bogus; return a dummy entry - return CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId); - } - - /// - /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. - /// This function wraps the logic necessary to keep the private - /// SystemTimeZones cache in working order - /// - /// This function will either return a valid TimeZoneInfo instance or - /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'. - /// - public static TimeZoneInfo FindSystemTimeZoneById(string id) - { - // Special case for Utc as it will not exist in the dictionary with the rest - // of the system time zones. There is no need to do this check for Local.Id - // since Local is a real time zone that exists in the dictionary cache - if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase)) - { - return Utc; - } - - if (id == null) - { - throw new ArgumentNullException(nameof(id)); - } - if (id.Length == 0 || id.Length > MaxKeyLength || id.Contains('\0')) - { - throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id)); - } - - TimeZoneInfo? value; - Exception? e; - - TimeZoneInfoResult result; - - CachedData cachedData = s_cachedData; - - lock (cachedData) - { - result = TryGetTimeZone(id, false, out value, out e, cachedData); - } - - if (result == TimeZoneInfoResult.Success) - { - return value!; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 - } - else if (result == TimeZoneInfoResult.InvalidTimeZoneException) - { - throw new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidRegistryData, id), e); - } - else if (result == TimeZoneInfoResult.SecurityException) - { - throw new SecurityException(SR.Format(SR.Security_CannotReadRegistryData, id), e); - } - else - { - throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e); - } - } - - // DateTime.Now fast path that avoids allocating an historically accurate TimeZoneInfo.Local and just creates a 1-year (current year) accurate time zone - internal static TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out bool isAmbiguousLocalDst) - { - bool isDaylightSavings = false; - isAmbiguousLocalDst = false; - TimeSpan baseOffset; - int timeYear = time.Year; - - OffsetAndRule match = s_cachedData.GetOneYearLocalFromUtc(timeYear); - baseOffset = match.Offset; - - if (match.Rule != null) - { - baseOffset = baseOffset + match.Rule.BaseUtcOffsetDelta; - if (match.Rule.HasDaylightSaving) - { - isDaylightSavings = GetIsDaylightSavingsFromUtc(time, timeYear, match.Offset, match.Rule, null, out isAmbiguousLocalDst, Local); - baseOffset += (isDaylightSavings ? match.Rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */); - } - } - return baseOffset; - } - - /// - /// Converts a REG_TZI_FORMAT struct to a TransitionTime - /// - When the argument 'readStart' is true the corresponding daylightTransitionTimeStart field is read - /// - When the argument 'readStart' is false the corresponding dayightTransitionTimeEnd field is read - /// - private static bool TransitionTimeFromTimeZoneInformation(in REG_TZI_FORMAT timeZoneInformation, out TransitionTime transitionTime, bool readStartDate) - { - // - // SYSTEMTIME - - // - // If the time zone does not support daylight saving time or if the caller needs - // to disable daylight saving time, the wMonth member in the SYSTEMTIME structure - // must be zero. If this date is specified, the DaylightDate value in the - // TIME_ZONE_INFORMATION structure must also be specified. Otherwise, the system - // assumes the time zone data is invalid and no changes will be applied. - // - bool supportsDst = (timeZoneInformation.StandardDate.Month != 0); - - if (!supportsDst) - { - transitionTime = default; - return false; - } - - // - // SYSTEMTIME - - // - // * FixedDateRule - - // If the Year member is not zero, the transition date is absolute; it will only occur one time - // - // * FloatingDateRule - - // To select the correct day in the month, set the Year member to zero, the Hour and Minute - // members to the transition time, the DayOfWeek member to the appropriate weekday, and the - // Day member to indicate the occurence of the day of the week within the month (first through fifth). - // - // Using this notation, specify the 2:00a.m. on the first Sunday in April as follows: - // Hour = 2, - // Month = 4, - // DayOfWeek = 0, - // Day = 1. - // - // Specify 2:00a.m. on the last Thursday in October as follows: - // Hour = 2, - // Month = 10, - // DayOfWeek = 4, - // Day = 5. - // - if (readStartDate) - { - // - // read the "daylightTransitionStart" - // - if (timeZoneInformation.DaylightDate.Year == 0) - { - transitionTime = TransitionTime.CreateFloatingDateRule( - new DateTime(1, /* year */ - 1, /* month */ - 1, /* day */ - timeZoneInformation.DaylightDate.Hour, - timeZoneInformation.DaylightDate.Minute, - timeZoneInformation.DaylightDate.Second, - timeZoneInformation.DaylightDate.Milliseconds), - timeZoneInformation.DaylightDate.Month, - timeZoneInformation.DaylightDate.Day, /* Week 1-5 */ - (DayOfWeek)timeZoneInformation.DaylightDate.DayOfWeek); - } - else - { - transitionTime = TransitionTime.CreateFixedDateRule( - new DateTime(1, /* year */ - 1, /* month */ - 1, /* day */ - timeZoneInformation.DaylightDate.Hour, - timeZoneInformation.DaylightDate.Minute, - timeZoneInformation.DaylightDate.Second, - timeZoneInformation.DaylightDate.Milliseconds), - timeZoneInformation.DaylightDate.Month, - timeZoneInformation.DaylightDate.Day); - } - } - else - { - // - // read the "daylightTransitionEnd" - // - if (timeZoneInformation.StandardDate.Year == 0) - { - transitionTime = TransitionTime.CreateFloatingDateRule( - new DateTime(1, /* year */ - 1, /* month */ - 1, /* day */ - timeZoneInformation.StandardDate.Hour, - timeZoneInformation.StandardDate.Minute, - timeZoneInformation.StandardDate.Second, - timeZoneInformation.StandardDate.Milliseconds), - timeZoneInformation.StandardDate.Month, - timeZoneInformation.StandardDate.Day, /* Week 1-5 */ - (DayOfWeek)timeZoneInformation.StandardDate.DayOfWeek); - } - else - { - transitionTime = TransitionTime.CreateFixedDateRule( - new DateTime(1, /* year */ - 1, /* month */ - 1, /* day */ - timeZoneInformation.StandardDate.Hour, - timeZoneInformation.StandardDate.Minute, - timeZoneInformation.StandardDate.Second, - timeZoneInformation.StandardDate.Milliseconds), - timeZoneInformation.StandardDate.Month, - timeZoneInformation.StandardDate.Day); - } - } - - return true; - } - - /// - /// Helper function that takes: - /// 1. A string representing a time_zone_name registry key name. - /// 2. A REG_TZI_FORMAT struct containing the default rule. - /// 3. An AdjustmentRule[] out-parameter. - /// - private static bool TryCreateAdjustmentRules(string id, in REG_TZI_FORMAT defaultTimeZoneInformation, out AdjustmentRule[]? rules, out Exception? e, int defaultBaseUtcOffset) - { - rules = null; - e = null; - - try - { - // Optional, Dynamic Time Zone Registry Data - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // - // HKLM - // Software - // Microsoft - // Windows NT - // CurrentVersion - // Time Zones - // - // Dynamic DST - // * "FirstEntry" REG_DWORD "1980" - // First year in the table. If the current year is less than this value, - // this entry will be used for DST boundaries - // * "LastEntry" REG_DWORD "2038" - // Last year in the table. If the current year is greater than this value, - // this entry will be used for DST boundaries" - // * "" REG_BINARY REG_TZI_FORMAT - // * "" REG_BINARY REG_TZI_FORMAT - // * "" REG_BINARY REG_TZI_FORMAT - // - using (RegistryKey? dynamicKey = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id + "\\Dynamic DST", writable: false)) - { - if (dynamicKey == null) - { - AdjustmentRule? rule = CreateAdjustmentRuleFromTimeZoneInformation( - defaultTimeZoneInformation, DateTime.MinValue.Date, DateTime.MaxValue.Date, defaultBaseUtcOffset); - if (rule != null) - { - rules = new[] { rule }; - } - return true; - } - - // - // loop over all of the "\Dynamic DST" hive entries - // - // read FirstEntry {MinValue - (year1, 12, 31)} - // read MiddleEntry {(yearN, 1, 1) - (yearN, 12, 31)} - // read LastEntry {(yearN, 1, 1) - MaxValue } - - // read the FirstEntry and LastEntry key values (ex: "1980", "2038") - int first = (int)dynamicKey.GetValue(FirstEntryValue, -1)!; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/34976 - int last = (int)dynamicKey.GetValue(LastEntryValue, -1)!; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/34976 - - if (first == -1 || last == -1 || first > last) - { - return false; - } - - // read the first year entry - REG_TZI_FORMAT dtzi; - - if (!TryGetTimeZoneEntryFromRegistry(dynamicKey, first.ToString(CultureInfo.InvariantCulture), out dtzi)) - { - return false; - } - - if (first == last) - { - // there is just 1 dynamic rule for this time zone. - AdjustmentRule? rule = CreateAdjustmentRuleFromTimeZoneInformation(dtzi, DateTime.MinValue.Date, DateTime.MaxValue.Date, defaultBaseUtcOffset); - if (rule != null) - { - rules = new[] { rule }; - } - return true; - } - - List rulesList = new List(1); - - // there are more than 1 dynamic rules for this time zone. - AdjustmentRule? firstRule = CreateAdjustmentRuleFromTimeZoneInformation( - dtzi, - DateTime.MinValue.Date, // MinValue - new DateTime(first, 12, 31), // December 31, - defaultBaseUtcOffset); - - if (firstRule != null) - { - rulesList.Add(firstRule); - } - - // read the middle year entries - for (int i = first + 1; i < last; i++) - { - if (!TryGetTimeZoneEntryFromRegistry(dynamicKey, i.ToString(CultureInfo.InvariantCulture), out dtzi)) - { - return false; - } - AdjustmentRule? middleRule = CreateAdjustmentRuleFromTimeZoneInformation( - dtzi, - new DateTime(i, 1, 1), // January 01, - new DateTime(i, 12, 31), // December 31, - defaultBaseUtcOffset); - - if (middleRule != null) - { - rulesList.Add(middleRule); - } - } - - // read the last year entry - if (!TryGetTimeZoneEntryFromRegistry(dynamicKey, last.ToString(CultureInfo.InvariantCulture), out dtzi)) - { - return false; - } - AdjustmentRule? lastRule = CreateAdjustmentRuleFromTimeZoneInformation( - dtzi, - new DateTime(last, 1, 1), // January 01, - DateTime.MaxValue.Date, // MaxValue - defaultBaseUtcOffset); - - if (lastRule != null) - { - rulesList.Add(lastRule); - } - - // convert the List to an AdjustmentRule array - if (rulesList.Count != 0) - { - rules = rulesList.ToArray(); - } - } // end of: using (RegistryKey dynamicKey... - } - catch (InvalidCastException ex) - { - // one of the RegistryKey.GetValue calls could not be cast to an expected value type - e = ex; - return false; - } - catch (ArgumentOutOfRangeException ex) - { - e = ex; - return false; - } - catch (ArgumentException ex) - { - e = ex; - return false; - } - return true; - } - - private static unsafe bool TryGetTimeZoneEntryFromRegistry(RegistryKey key, string name, out REG_TZI_FORMAT dtzi) - { - if (!(key.GetValue(name, null) is byte[] regValue) || regValue.Length != sizeof(REG_TZI_FORMAT)) - { - dtzi = default; - return false; - } - fixed (byte * pBytes = ®Value[0]) - dtzi = *(REG_TZI_FORMAT *)pBytes; - return true; - } - - /// - /// Helper function that compares the StandardBias and StandardDate portion a - /// TimeZoneInformation struct to a time zone registry entry. - /// - private static bool TryCompareStandardDate(in TIME_ZONE_INFORMATION timeZone, in REG_TZI_FORMAT registryTimeZoneInfo) => - timeZone.Bias == registryTimeZoneInfo.Bias && - timeZone.StandardBias == registryTimeZoneInfo.StandardBias && - timeZone.StandardDate.Equals(registryTimeZoneInfo.StandardDate); - - /// - /// Helper function that compares a TimeZoneInformation struct to a time zone registry entry. - /// - private static bool TryCompareTimeZoneInformationToRegistry(in TIME_ZONE_INFORMATION timeZone, string id, out bool dstDisabled) - { - dstDisabled = false; - - using (RegistryKey? key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id, writable: false)) - { - if (key == null) - { - return false; - } - - REG_TZI_FORMAT registryTimeZoneInfo; - if (!TryGetTimeZoneEntryFromRegistry(key, TimeZoneInfoValue, out registryTimeZoneInfo)) - { - return false; - } - - // - // first compare the bias and standard date information between the data from the Win32 API - // and the data from the registry... - // - bool result = TryCompareStandardDate(timeZone, registryTimeZoneInfo); - - if (!result) - { - return false; - } - - result = dstDisabled || CheckDaylightSavingTimeNotSupported(timeZone) || - // - // since Daylight Saving Time is not "disabled", do a straight comparision between - // the Win32 API data and the registry data ... - // - (timeZone.DaylightBias == registryTimeZoneInfo.DaylightBias && - timeZone.DaylightDate.Equals(registryTimeZoneInfo.DaylightDate)); - - // Finally compare the "StandardName" string value... - // - // we do not compare "DaylightName" as this TimeZoneInformation field may contain - // either "StandardName" or "DaylightName" depending on the time of year and current machine settings - // - if (result) - { - string? registryStandardName = key.GetValue(StandardValue, string.Empty) as string; - result = string.Equals(registryStandardName, timeZone.GetStandardName(), StringComparison.Ordinal); - } - return result; - } - } - - /// - /// Helper function for retrieving a localized string resource via MUI. - /// The function expects a string in the form: "@resource.dll, -123" - /// - /// "resource.dll" is a language-neutral portable executable (LNPE) file in - /// the %windir%\system32 directory. The OS is queried to find the best-fit - /// localized resource file for this LNPE (ex: %windir%\system32\en-us\resource.dll.mui). - /// If a localized resource file exists, we LoadString resource ID "123" and - /// return it to our caller. - /// - private static string TryGetLocalizedNameByMuiNativeResource(string resource) - { - if (string.IsNullOrEmpty(resource)) - { - return string.Empty; - } - - // parse "@tzres.dll, -100" - // - // filePath = "C:\Windows\System32\tzres.dll" - // resourceId = -100 - // - string[] resources = resource.Split(','); - if (resources.Length != 2) - { - return string.Empty; - } - - string filePath; - int resourceId; - - // get the path to Windows\System32 - string system32 = Environment.SystemDirectory; - - // trim the string "@tzres.dll" => "tzres.dll" - string tzresDll = resources[0].TrimStart('@'); - - try - { - filePath = Path.Combine(system32, tzresDll); - } - catch (ArgumentException) - { - // there were probably illegal characters in the path - return string.Empty; - } - - if (!int.TryParse(resources[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out resourceId)) - { - return string.Empty; - } - resourceId = -resourceId; - - try - { - unsafe - { - char* fileMuiPath = stackalloc char[Interop.Kernel32.MAX_PATH]; - int fileMuiPathLength = Interop.Kernel32.MAX_PATH; - int languageLength = 0; - long enumerator = 0; - - bool succeeded = Interop.Kernel32.GetFileMUIPath( - Interop.Kernel32.MUI_PREFERRED_UI_LANGUAGES, - filePath, null /* language */, ref languageLength, - fileMuiPath, ref fileMuiPathLength, ref enumerator); - return succeeded ? - TryGetLocalizedNameByNativeResource(new string(fileMuiPath, 0, fileMuiPathLength), resourceId) : - string.Empty; - } - } - catch (EntryPointNotFoundException) - { - return string.Empty; - } - } - - /// - /// Helper function for retrieving a localized string resource via a native resource DLL. - /// The function expects a string in the form: "C:\Windows\System32\en-us\resource.dll" - /// - /// "resource.dll" is a language-specific resource DLL. - /// If the localized resource DLL exists, LoadString(resource) is returned. - /// - private static unsafe string TryGetLocalizedNameByNativeResource(string filePath, int resource) - { - using (SafeLibraryHandle handle = Interop.Kernel32.LoadLibraryEx(filePath, IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_AS_DATAFILE)) - { - if (!handle.IsInvalid) - { - const int LoadStringMaxLength = 500; - char* localizedResource = stackalloc char[LoadStringMaxLength]; - - int charsWritten = Interop.User32.LoadString(handle, (uint)resource, localizedResource, LoadStringMaxLength); - if (charsWritten != 0) - { - return new string(localizedResource, 0, charsWritten); - } - } - } - - return string.Empty; - } - - /// - /// Helper function for retrieving the DisplayName, StandardName, and DaylightName from the registry - /// - /// The function first checks the MUI_ key-values, and if they exist, it loads the strings from the MUI - /// resource dll(s). When the keys do not exist, the function falls back to reading from the standard - /// key-values - /// - private static void GetLocalizedNamesByRegistryKey(RegistryKey key, out string? displayName, out string? standardName, out string? daylightName) - { - displayName = string.Empty; - standardName = string.Empty; - daylightName = string.Empty; - - // read the MUI_ registry keys - string? displayNameMuiResource = key.GetValue(MuiDisplayValue, string.Empty) as string; - string? standardNameMuiResource = key.GetValue(MuiStandardValue, string.Empty) as string; - string? daylightNameMuiResource = key.GetValue(MuiDaylightValue, string.Empty) as string; - - // try to load the strings from the native resource DLL(s) - if (!string.IsNullOrEmpty(displayNameMuiResource)) - { - displayName = TryGetLocalizedNameByMuiNativeResource(displayNameMuiResource); - } - - if (!string.IsNullOrEmpty(standardNameMuiResource)) - { - standardName = TryGetLocalizedNameByMuiNativeResource(standardNameMuiResource); - } - - if (!string.IsNullOrEmpty(daylightNameMuiResource)) - { - daylightName = TryGetLocalizedNameByMuiNativeResource(daylightNameMuiResource); - } - - // fallback to using the standard registry keys - if (string.IsNullOrEmpty(displayName)) - { - displayName = key.GetValue(DisplayValue, string.Empty) as string; - } - if (string.IsNullOrEmpty(standardName)) - { - standardName = key.GetValue(StandardValue, string.Empty) as string; - } - if (string.IsNullOrEmpty(daylightName)) - { - daylightName = key.GetValue(DaylightValue, string.Empty) as string; - } - } - - /// - /// Helper function that takes a string representing a time_zone_name registry key name - /// and returns a TimeZoneInfo instance. - /// - private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out TimeZoneInfo? value, out Exception? e) - { - e = null; - - // Standard Time Zone Registry Data - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - // HKLM - // Software - // Microsoft - // Windows NT - // CurrentVersion - // Time Zones - // - // * STD, REG_SZ "Standard Time Name" - // (For OS installed zones, this will always be English) - // * MUI_STD, REG_SZ "@tzres.dll,-1234" - // Indirect string to localized resource for Standard Time, - // add "%windir%\system32\" after "@" - // * DLT, REG_SZ "Daylight Time Name" - // (For OS installed zones, this will always be English) - // * MUI_DLT, REG_SZ "@tzres.dll,-1234" - // Indirect string to localized resource for Daylight Time, - // add "%windir%\system32\" after "@" - // * Display, REG_SZ "Display Name like (GMT-8:00) Pacific Time..." - // * MUI_Display, REG_SZ "@tzres.dll,-1234" - // Indirect string to localized resource for the Display, - // add "%windir%\system32\" after "@" - // * TZI, REG_BINARY REG_TZI_FORMAT - // - using (RegistryKey? key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id, writable: false)) - { - if (key == null) - { - value = null; - return TimeZoneInfoResult.TimeZoneNotFoundException; - } - - REG_TZI_FORMAT defaultTimeZoneInformation; - if (!TryGetTimeZoneEntryFromRegistry(key, TimeZoneInfoValue, out defaultTimeZoneInformation)) - { - // the registry value could not be cast to a byte array - value = null; - return TimeZoneInfoResult.InvalidTimeZoneException; - } - - AdjustmentRule[]? adjustmentRules; - if (!TryCreateAdjustmentRules(id, defaultTimeZoneInformation, out adjustmentRules, out e, defaultTimeZoneInformation.Bias)) - { - value = null; - return TimeZoneInfoResult.InvalidTimeZoneException; - } - - GetLocalizedNamesByRegistryKey(key, out string? displayName, out string? standardName, out string? daylightName); - - try - { - value = new TimeZoneInfo( - id, - new TimeSpan(0, -(defaultTimeZoneInformation.Bias), 0), - displayName, - standardName, - daylightName, - adjustmentRules, - disableDaylightSavingTime: false); - - return TimeZoneInfoResult.Success; - } - catch (ArgumentException ex) - { - // TimeZoneInfo constructor can throw ArgumentException and InvalidTimeZoneException - value = null; - e = ex; - return TimeZoneInfoResult.InvalidTimeZoneException; - } - catch (InvalidTimeZoneException ex) - { - // TimeZoneInfo constructor can throw ArgumentException and InvalidTimeZoneException - value = null; - e = ex; - return TimeZoneInfoResult.InvalidTimeZoneException; - } - } - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; +using System.Threading; + +using Microsoft.Win32.SafeHandles; + +using Internal.Win32; +using Internal.Runtime.CompilerServices; + +using REG_TZI_FORMAT = Interop.Kernel32.REG_TZI_FORMAT; +using TIME_ZONE_INFORMATION = Interop.Kernel32.TIME_ZONE_INFORMATION; +using TIME_DYNAMIC_ZONE_INFORMATION = Interop.Kernel32.TIME_DYNAMIC_ZONE_INFORMATION; + +namespace System +{ + public sealed partial class TimeZoneInfo + { + // registry constants for the 'Time Zones' hive + // + private const string TimeZonesRegistryHive = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"; + private const string DisplayValue = "Display"; + private const string DaylightValue = "Dlt"; + private const string StandardValue = "Std"; + private const string MuiDisplayValue = "MUI_Display"; + private const string MuiDaylightValue = "MUI_Dlt"; + private const string MuiStandardValue = "MUI_Std"; + private const string TimeZoneInfoValue = "TZI"; + private const string FirstEntryValue = "FirstEntry"; + private const string LastEntryValue = "LastEntry"; + + private const int MaxKeyLength = 255; + + private sealed partial class CachedData + { + private static TimeZoneInfo GetCurrentOneYearLocal() + { + // load the data from the OS + TIME_ZONE_INFORMATION timeZoneInformation; + uint result = Interop.Kernel32.GetTimeZoneInformation(out timeZoneInformation); + return result == Interop.Kernel32.TIME_ZONE_ID_INVALID ? + CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId) : + GetLocalTimeZoneFromWin32Data(timeZoneInformation, dstDisabled: false); + } + + private volatile OffsetAndRule? _oneYearLocalFromUtc; + + public OffsetAndRule GetOneYearLocalFromUtc(int year) + { + OffsetAndRule? oneYearLocFromUtc = _oneYearLocalFromUtc; + if (oneYearLocFromUtc == null || oneYearLocFromUtc.Year != year) + { + TimeZoneInfo currentYear = GetCurrentOneYearLocal(); + AdjustmentRule? rule = currentYear._adjustmentRules == null ? null : currentYear._adjustmentRules[0]; + oneYearLocFromUtc = new OffsetAndRule(year, currentYear.BaseUtcOffset, rule); + _oneYearLocalFromUtc = oneYearLocFromUtc; + } + return oneYearLocFromUtc; + } + } + + private sealed class OffsetAndRule + { + public readonly int Year; + public readonly TimeSpan Offset; + public readonly AdjustmentRule? Rule; + + public OffsetAndRule(int year, TimeSpan offset, AdjustmentRule? rule) + { + Year = year; + Offset = offset; + Rule = rule; + } + } + + /// + /// Returns a cloned array of AdjustmentRule objects + /// + public AdjustmentRule[] GetAdjustmentRules() + { + if (_adjustmentRules == null) + { + return Array.Empty(); + } + + return (AdjustmentRule[])_adjustmentRules.Clone(); + } + + private static void PopulateAllSystemTimeZones(CachedData cachedData) + { + Debug.Assert(Monitor.IsEntered(cachedData)); + + using (RegistryKey? reg = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive, writable: false)) + { + if (reg != null) + { + foreach (string keyName in reg.GetSubKeyNames()) + { + TryGetTimeZone(keyName, false, out _, out _, cachedData); // populate the cache + } + } + } + } + + private TimeZoneInfo(in TIME_ZONE_INFORMATION zone, bool dstDisabled) + { + string standardName = zone.GetStandardName(); + if (standardName.Length == 0) + { + _id = LocalId; // the ID must contain at least 1 character - initialize _id to "Local" + } + else + { + _id = standardName; + } + _baseUtcOffset = new TimeSpan(0, -(zone.Bias), 0); + + if (!dstDisabled) + { + // only create the adjustment rule if DST is enabled + REG_TZI_FORMAT regZone = new REG_TZI_FORMAT(zone); + AdjustmentRule? rule = CreateAdjustmentRuleFromTimeZoneInformation(regZone, DateTime.MinValue.Date, DateTime.MaxValue.Date, zone.Bias); + if (rule != null) + { + _adjustmentRules = new[] { rule }; + } + } + + ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out _supportsDaylightSavingTime); + _displayName = standardName; + _standardDisplayName = standardName; + _daylightDisplayName = zone.GetDaylightName(); + } + + /// + /// Helper function to check if the current TimeZoneInformation struct does not support DST. + /// This check returns true when the DaylightDate == StandardDate. + /// This check is only meant to be used for "Local". + /// + private static bool CheckDaylightSavingTimeNotSupported(in TIME_ZONE_INFORMATION timeZone) => + timeZone.DaylightDate.Equals(timeZone.StandardDate); + + /// + /// Converts a REG_TZI_FORMAT struct to an AdjustmentRule. + /// + private static AdjustmentRule? CreateAdjustmentRuleFromTimeZoneInformation(in REG_TZI_FORMAT timeZoneInformation, DateTime startDate, DateTime endDate, int defaultBaseUtcOffset) + { + bool supportsDst = timeZoneInformation.StandardDate.Month != 0; + + if (!supportsDst) + { + if (timeZoneInformation.Bias == defaultBaseUtcOffset) + { + // this rule will not contain any information to be used to adjust dates. just ignore it + return null; + } + + return AdjustmentRule.CreateAdjustmentRule( + startDate, + endDate, + TimeSpan.Zero, // no daylight saving transition + TransitionTime.CreateFixedDateRule(DateTime.MinValue, 1, 1), + TransitionTime.CreateFixedDateRule(DateTime.MinValue.AddMilliseconds(1), 1, 1), + new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0), // Bias delta is all what we need from this rule + noDaylightTransitions: false); + } + + // + // Create an AdjustmentRule with TransitionTime objects + // + TransitionTime daylightTransitionStart; + if (!TransitionTimeFromTimeZoneInformation(timeZoneInformation, out daylightTransitionStart, readStartDate: true)) + { + return null; + } + + TransitionTime daylightTransitionEnd; + if (!TransitionTimeFromTimeZoneInformation(timeZoneInformation, out daylightTransitionEnd, readStartDate: false)) + { + return null; + } + + if (daylightTransitionStart.Equals(daylightTransitionEnd)) + { + // this happens when the time zone does support DST but the OS has DST disabled + return null; + } + + return AdjustmentRule.CreateAdjustmentRule( + startDate, + endDate, + new TimeSpan(0, -timeZoneInformation.DaylightBias, 0), + daylightTransitionStart, + daylightTransitionEnd, + new TimeSpan(0, defaultBaseUtcOffset - timeZoneInformation.Bias, 0), + noDaylightTransitions: false); + } + + /// + /// Helper function that searches the registry for a time zone entry + /// that matches the TimeZoneInformation struct. + /// + private static string? FindIdFromTimeZoneInformation(in TIME_ZONE_INFORMATION timeZone, out bool dstDisabled) + { + dstDisabled = false; + + using (RegistryKey? key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive, writable: false)) + { + if (key == null) + { + return null; + } + + foreach (string keyName in key.GetSubKeyNames()) + { + if (TryCompareTimeZoneInformationToRegistry(timeZone, keyName, out dstDisabled)) + { + return keyName; + } + } + } + + return null; + } + + /// + /// Helper function for retrieving the local system time zone. + /// May throw COMException, TimeZoneNotFoundException, InvalidTimeZoneException. + /// Assumes cachedData lock is taken. + /// + /// A new TimeZoneInfo instance. + private static TimeZoneInfo GetLocalTimeZone(CachedData cachedData) + { + Debug.Assert(Monitor.IsEntered(cachedData)); + + // + // Try using the "kernel32!GetDynamicTimeZoneInformation" API to get the "id" + // + var dynamicTimeZoneInformation = new TIME_DYNAMIC_ZONE_INFORMATION(); + + // call kernel32!GetDynamicTimeZoneInformation... + uint result = Interop.Kernel32.GetDynamicTimeZoneInformation(out dynamicTimeZoneInformation); + if (result == Interop.Kernel32.TIME_ZONE_ID_INVALID) + { + // return a dummy entry + return CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId); + } + + // check to see if we can use the key name returned from the API call + string dynamicTimeZoneKeyName = dynamicTimeZoneInformation.GetTimeZoneKeyName(); + if (dynamicTimeZoneKeyName.Length != 0) + { + if (TryGetTimeZone(dynamicTimeZoneKeyName, dynamicTimeZoneInformation.DynamicDaylightTimeDisabled != 0, out TimeZoneInfo? zone, out _, cachedData) == TimeZoneInfoResult.Success) + { + // successfully loaded the time zone from the registry + return zone!; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 + } + } + + var timeZoneInformation = new TIME_ZONE_INFORMATION(dynamicTimeZoneInformation); + + // the key name was not returned or it pointed to a bogus entry - search for the entry ourselves + string? id = FindIdFromTimeZoneInformation(timeZoneInformation, out bool dstDisabled); + + if (id != null) + { + if (TryGetTimeZone(id, dstDisabled, out TimeZoneInfo? zone, out _, cachedData) == TimeZoneInfoResult.Success) + { + // successfully loaded the time zone from the registry + return zone!; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 + } + } + + // We could not find the data in the registry. Fall back to using + // the data from the Win32 API + return GetLocalTimeZoneFromWin32Data(timeZoneInformation, dstDisabled); + } + + /// + /// Helper function used by 'GetLocalTimeZone()' - this function wraps a bunch of + /// try/catch logic for handling the TimeZoneInfo private constructor that takes + /// a TIME_ZONE_INFORMATION structure. + /// + private static TimeZoneInfo GetLocalTimeZoneFromWin32Data(in TIME_ZONE_INFORMATION timeZoneInformation, bool dstDisabled) + { + // first try to create the TimeZoneInfo with the original 'dstDisabled' flag + try + { + return new TimeZoneInfo(timeZoneInformation, dstDisabled); + } + catch (ArgumentException) { } + catch (InvalidTimeZoneException) { } + + // if 'dstDisabled' was false then try passing in 'true' as a last ditch effort + if (!dstDisabled) + { + try + { + return new TimeZoneInfo(timeZoneInformation, dstDisabled: true); + } + catch (ArgumentException) { } + catch (InvalidTimeZoneException) { } + } + + // the data returned from Windows is completely bogus; return a dummy entry + return CreateCustomTimeZone(LocalId, TimeSpan.Zero, LocalId, LocalId); + } + + /// + /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. + /// This function wraps the logic necessary to keep the private + /// SystemTimeZones cache in working order + /// + /// This function will either return a valid TimeZoneInfo instance or + /// it will throw 'InvalidTimeZoneException' / 'TimeZoneNotFoundException'. + /// + public static TimeZoneInfo FindSystemTimeZoneById(string id) + { + // Special case for Utc as it will not exist in the dictionary with the rest + // of the system time zones. There is no need to do this check for Local.Id + // since Local is a real time zone that exists in the dictionary cache + if (string.Equals(id, UtcId, StringComparison.OrdinalIgnoreCase)) + { + return Utc; + } + + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + if (id.Length == 0 || id.Length > MaxKeyLength || id.Contains('\0')) + { + throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id)); + } + + TimeZoneInfo? value; + Exception? e; + + TimeZoneInfoResult result; + + CachedData cachedData = s_cachedData; + + lock (cachedData) + { + result = TryGetTimeZone(id, false, out value, out e, cachedData); + } + + if (result == TimeZoneInfoResult.Success) + { + return value!; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 + } + else if (result == TimeZoneInfoResult.InvalidTimeZoneException) + { + throw new InvalidTimeZoneException(SR.Format(SR.InvalidTimeZone_InvalidRegistryData, id), e); + } + else if (result == TimeZoneInfoResult.SecurityException) + { + throw new SecurityException(SR.Format(SR.Security_CannotReadRegistryData, id), e); + } + else + { + throw new TimeZoneNotFoundException(SR.Format(SR.TimeZoneNotFound_MissingData, id), e); + } + } + + // DateTime.Now fast path that avoids allocating an historically accurate TimeZoneInfo.Local and just creates a 1-year (current year) accurate time zone + internal static TimeSpan GetDateTimeNowUtcOffsetFromUtc(DateTime time, out bool isAmbiguousLocalDst) + { + bool isDaylightSavings = false; + isAmbiguousLocalDst = false; + TimeSpan baseOffset; + int timeYear = time.Year; + + OffsetAndRule match = s_cachedData.GetOneYearLocalFromUtc(timeYear); + baseOffset = match.Offset; + + if (match.Rule != null) + { + baseOffset = baseOffset + match.Rule.BaseUtcOffsetDelta; + if (match.Rule.HasDaylightSaving) + { + isDaylightSavings = GetIsDaylightSavingsFromUtc(time, timeYear, match.Offset, match.Rule, null, out isAmbiguousLocalDst, Local); + baseOffset += (isDaylightSavings ? match.Rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */); + } + } + return baseOffset; + } + + /// + /// Converts a REG_TZI_FORMAT struct to a TransitionTime + /// - When the argument 'readStart' is true the corresponding daylightTransitionTimeStart field is read + /// - When the argument 'readStart' is false the corresponding dayightTransitionTimeEnd field is read + /// + private static bool TransitionTimeFromTimeZoneInformation(in REG_TZI_FORMAT timeZoneInformation, out TransitionTime transitionTime, bool readStartDate) + { + // + // SYSTEMTIME - + // + // If the time zone does not support daylight saving time or if the caller needs + // to disable daylight saving time, the wMonth member in the SYSTEMTIME structure + // must be zero. If this date is specified, the DaylightDate value in the + // TIME_ZONE_INFORMATION structure must also be specified. Otherwise, the system + // assumes the time zone data is invalid and no changes will be applied. + // + bool supportsDst = (timeZoneInformation.StandardDate.Month != 0); + + if (!supportsDst) + { + transitionTime = default; + return false; + } + + // + // SYSTEMTIME - + // + // * FixedDateRule - + // If the Year member is not zero, the transition date is absolute; it will only occur one time + // + // * FloatingDateRule - + // To select the correct day in the month, set the Year member to zero, the Hour and Minute + // members to the transition time, the DayOfWeek member to the appropriate weekday, and the + // Day member to indicate the occurence of the day of the week within the month (first through fifth). + // + // Using this notation, specify the 2:00a.m. on the first Sunday in April as follows: + // Hour = 2, + // Month = 4, + // DayOfWeek = 0, + // Day = 1. + // + // Specify 2:00a.m. on the last Thursday in October as follows: + // Hour = 2, + // Month = 10, + // DayOfWeek = 4, + // Day = 5. + // + if (readStartDate) + { + // + // read the "daylightTransitionStart" + // + if (timeZoneInformation.DaylightDate.Year == 0) + { + transitionTime = TransitionTime.CreateFloatingDateRule( + new DateTime(1, /* year */ + 1, /* month */ + 1, /* day */ + timeZoneInformation.DaylightDate.Hour, + timeZoneInformation.DaylightDate.Minute, + timeZoneInformation.DaylightDate.Second, + timeZoneInformation.DaylightDate.Milliseconds), + timeZoneInformation.DaylightDate.Month, + timeZoneInformation.DaylightDate.Day, /* Week 1-5 */ + (DayOfWeek)timeZoneInformation.DaylightDate.DayOfWeek); + } + else + { + transitionTime = TransitionTime.CreateFixedDateRule( + new DateTime(1, /* year */ + 1, /* month */ + 1, /* day */ + timeZoneInformation.DaylightDate.Hour, + timeZoneInformation.DaylightDate.Minute, + timeZoneInformation.DaylightDate.Second, + timeZoneInformation.DaylightDate.Milliseconds), + timeZoneInformation.DaylightDate.Month, + timeZoneInformation.DaylightDate.Day); + } + } + else + { + // + // read the "daylightTransitionEnd" + // + if (timeZoneInformation.StandardDate.Year == 0) + { + transitionTime = TransitionTime.CreateFloatingDateRule( + new DateTime(1, /* year */ + 1, /* month */ + 1, /* day */ + timeZoneInformation.StandardDate.Hour, + timeZoneInformation.StandardDate.Minute, + timeZoneInformation.StandardDate.Second, + timeZoneInformation.StandardDate.Milliseconds), + timeZoneInformation.StandardDate.Month, + timeZoneInformation.StandardDate.Day, /* Week 1-5 */ + (DayOfWeek)timeZoneInformation.StandardDate.DayOfWeek); + } + else + { + transitionTime = TransitionTime.CreateFixedDateRule( + new DateTime(1, /* year */ + 1, /* month */ + 1, /* day */ + timeZoneInformation.StandardDate.Hour, + timeZoneInformation.StandardDate.Minute, + timeZoneInformation.StandardDate.Second, + timeZoneInformation.StandardDate.Milliseconds), + timeZoneInformation.StandardDate.Month, + timeZoneInformation.StandardDate.Day); + } + } + + return true; + } + + /// + /// Helper function that takes: + /// 1. A string representing a time_zone_name registry key name. + /// 2. A REG_TZI_FORMAT struct containing the default rule. + /// 3. An AdjustmentRule[] out-parameter. + /// + private static bool TryCreateAdjustmentRules(string id, in REG_TZI_FORMAT defaultTimeZoneInformation, out AdjustmentRule[]? rules, out Exception? e, int defaultBaseUtcOffset) + { + rules = null; + e = null; + + try + { + // Optional, Dynamic Time Zone Registry Data + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + // + // HKLM + // Software + // Microsoft + // Windows NT + // CurrentVersion + // Time Zones + // + // Dynamic DST + // * "FirstEntry" REG_DWORD "1980" + // First year in the table. If the current year is less than this value, + // this entry will be used for DST boundaries + // * "LastEntry" REG_DWORD "2038" + // Last year in the table. If the current year is greater than this value, + // this entry will be used for DST boundaries" + // * "" REG_BINARY REG_TZI_FORMAT + // * "" REG_BINARY REG_TZI_FORMAT + // * "" REG_BINARY REG_TZI_FORMAT + // + using (RegistryKey? dynamicKey = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id + "\\Dynamic DST", writable: false)) + { + if (dynamicKey == null) + { + AdjustmentRule? rule = CreateAdjustmentRuleFromTimeZoneInformation( + defaultTimeZoneInformation, DateTime.MinValue.Date, DateTime.MaxValue.Date, defaultBaseUtcOffset); + if (rule != null) + { + rules = new[] { rule }; + } + return true; + } + + // + // loop over all of the "\Dynamic DST" hive entries + // + // read FirstEntry {MinValue - (year1, 12, 31)} + // read MiddleEntry {(yearN, 1, 1) - (yearN, 12, 31)} + // read LastEntry {(yearN, 1, 1) - MaxValue } + + // read the FirstEntry and LastEntry key values (ex: "1980", "2038") + int first = (int)dynamicKey.GetValue(FirstEntryValue, -1)!; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/34976 + int last = (int)dynamicKey.GetValue(LastEntryValue, -1)!; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/34976 + + if (first == -1 || last == -1 || first > last) + { + return false; + } + + // read the first year entry + REG_TZI_FORMAT dtzi; + + if (!TryGetTimeZoneEntryFromRegistry(dynamicKey, first.ToString(CultureInfo.InvariantCulture), out dtzi)) + { + return false; + } + + if (first == last) + { + // there is just 1 dynamic rule for this time zone. + AdjustmentRule? rule = CreateAdjustmentRuleFromTimeZoneInformation(dtzi, DateTime.MinValue.Date, DateTime.MaxValue.Date, defaultBaseUtcOffset); + if (rule != null) + { + rules = new[] { rule }; + } + return true; + } + + List rulesList = new List(1); + + // there are more than 1 dynamic rules for this time zone. + AdjustmentRule? firstRule = CreateAdjustmentRuleFromTimeZoneInformation( + dtzi, + DateTime.MinValue.Date, // MinValue + new DateTime(first, 12, 31), // December 31, + defaultBaseUtcOffset); + + if (firstRule != null) + { + rulesList.Add(firstRule); + } + + // read the middle year entries + for (int i = first + 1; i < last; i++) + { + if (!TryGetTimeZoneEntryFromRegistry(dynamicKey, i.ToString(CultureInfo.InvariantCulture), out dtzi)) + { + return false; + } + AdjustmentRule? middleRule = CreateAdjustmentRuleFromTimeZoneInformation( + dtzi, + new DateTime(i, 1, 1), // January 01, + new DateTime(i, 12, 31), // December 31, + defaultBaseUtcOffset); + + if (middleRule != null) + { + rulesList.Add(middleRule); + } + } + + // read the last year entry + if (!TryGetTimeZoneEntryFromRegistry(dynamicKey, last.ToString(CultureInfo.InvariantCulture), out dtzi)) + { + return false; + } + AdjustmentRule? lastRule = CreateAdjustmentRuleFromTimeZoneInformation( + dtzi, + new DateTime(last, 1, 1), // January 01, + DateTime.MaxValue.Date, // MaxValue + defaultBaseUtcOffset); + + if (lastRule != null) + { + rulesList.Add(lastRule); + } + + // convert the List to an AdjustmentRule array + if (rulesList.Count != 0) + { + rules = rulesList.ToArray(); + } + } // end of: using (RegistryKey dynamicKey... + } + catch (InvalidCastException ex) + { + // one of the RegistryKey.GetValue calls could not be cast to an expected value type + e = ex; + return false; + } + catch (ArgumentOutOfRangeException ex) + { + e = ex; + return false; + } + catch (ArgumentException ex) + { + e = ex; + return false; + } + return true; + } + + private static unsafe bool TryGetTimeZoneEntryFromRegistry(RegistryKey key, string name, out REG_TZI_FORMAT dtzi) + { + if (!(key.GetValue(name, null) is byte[] regValue) || regValue.Length != sizeof(REG_TZI_FORMAT)) + { + dtzi = default; + return false; + } + fixed (byte * pBytes = ®Value[0]) + dtzi = *(REG_TZI_FORMAT *)pBytes; + return true; + } + + /// + /// Helper function that compares the StandardBias and StandardDate portion a + /// TimeZoneInformation struct to a time zone registry entry. + /// + private static bool TryCompareStandardDate(in TIME_ZONE_INFORMATION timeZone, in REG_TZI_FORMAT registryTimeZoneInfo) => + timeZone.Bias == registryTimeZoneInfo.Bias && + timeZone.StandardBias == registryTimeZoneInfo.StandardBias && + timeZone.StandardDate.Equals(registryTimeZoneInfo.StandardDate); + + /// + /// Helper function that compares a TimeZoneInformation struct to a time zone registry entry. + /// + private static bool TryCompareTimeZoneInformationToRegistry(in TIME_ZONE_INFORMATION timeZone, string id, out bool dstDisabled) + { + dstDisabled = false; + + using (RegistryKey? key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id, writable: false)) + { + if (key == null) + { + return false; + } + + REG_TZI_FORMAT registryTimeZoneInfo; + if (!TryGetTimeZoneEntryFromRegistry(key, TimeZoneInfoValue, out registryTimeZoneInfo)) + { + return false; + } + + // + // first compare the bias and standard date information between the data from the Win32 API + // and the data from the registry... + // + bool result = TryCompareStandardDate(timeZone, registryTimeZoneInfo); + + if (!result) + { + return false; + } + + result = dstDisabled || CheckDaylightSavingTimeNotSupported(timeZone) || + // + // since Daylight Saving Time is not "disabled", do a straight comparision between + // the Win32 API data and the registry data ... + // + (timeZone.DaylightBias == registryTimeZoneInfo.DaylightBias && + timeZone.DaylightDate.Equals(registryTimeZoneInfo.DaylightDate)); + + // Finally compare the "StandardName" string value... + // + // we do not compare "DaylightName" as this TimeZoneInformation field may contain + // either "StandardName" or "DaylightName" depending on the time of year and current machine settings + // + if (result) + { + string? registryStandardName = key.GetValue(StandardValue, string.Empty) as string; + result = string.Equals(registryStandardName, timeZone.GetStandardName(), StringComparison.Ordinal); + } + return result; + } + } + + /// + /// Helper function for retrieving a localized string resource via MUI. + /// The function expects a string in the form: "@resource.dll, -123" + /// + /// "resource.dll" is a language-neutral portable executable (LNPE) file in + /// the %windir%\system32 directory. The OS is queried to find the best-fit + /// localized resource file for this LNPE (ex: %windir%\system32\en-us\resource.dll.mui). + /// If a localized resource file exists, we LoadString resource ID "123" and + /// return it to our caller. + /// + private static string TryGetLocalizedNameByMuiNativeResource(string resource) + { + if (string.IsNullOrEmpty(resource)) + { + return string.Empty; + } + + // parse "@tzres.dll, -100" + // + // filePath = "C:\Windows\System32\tzres.dll" + // resourceId = -100 + // + string[] resources = resource.Split(','); + if (resources.Length != 2) + { + return string.Empty; + } + + string filePath; + int resourceId; + + // get the path to Windows\System32 + string system32 = Environment.SystemDirectory; + + // trim the string "@tzres.dll" => "tzres.dll" + string tzresDll = resources[0].TrimStart('@'); + + try + { + filePath = Path.Combine(system32, tzresDll); + } + catch (ArgumentException) + { + // there were probably illegal characters in the path + return string.Empty; + } + + if (!int.TryParse(resources[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out resourceId)) + { + return string.Empty; + } + resourceId = -resourceId; + + try + { + unsafe + { + char* fileMuiPath = stackalloc char[Interop.Kernel32.MAX_PATH]; + int fileMuiPathLength = Interop.Kernel32.MAX_PATH; + int languageLength = 0; + long enumerator = 0; + + bool succeeded = Interop.Kernel32.GetFileMUIPath( + Interop.Kernel32.MUI_PREFERRED_UI_LANGUAGES, + filePath, null /* language */, ref languageLength, + fileMuiPath, ref fileMuiPathLength, ref enumerator); + return succeeded ? + TryGetLocalizedNameByNativeResource(new string(fileMuiPath, 0, fileMuiPathLength), resourceId) : + string.Empty; + } + } + catch (EntryPointNotFoundException) + { + return string.Empty; + } + } + + /// + /// Helper function for retrieving a localized string resource via a native resource DLL. + /// The function expects a string in the form: "C:\Windows\System32\en-us\resource.dll" + /// + /// "resource.dll" is a language-specific resource DLL. + /// If the localized resource DLL exists, LoadString(resource) is returned. + /// + private static unsafe string TryGetLocalizedNameByNativeResource(string filePath, int resource) + { + using (SafeLibraryHandle handle = Interop.Kernel32.LoadLibraryEx(filePath, IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_AS_DATAFILE)) + { + if (!handle.IsInvalid) + { + const int LoadStringMaxLength = 500; + char* localizedResource = stackalloc char[LoadStringMaxLength]; + + int charsWritten = Interop.User32.LoadString(handle, (uint)resource, localizedResource, LoadStringMaxLength); + if (charsWritten != 0) + { + return new string(localizedResource, 0, charsWritten); + } + } + } + + return string.Empty; + } + + /// + /// Helper function for retrieving the DisplayName, StandardName, and DaylightName from the registry + /// + /// The function first checks the MUI_ key-values, and if they exist, it loads the strings from the MUI + /// resource dll(s). When the keys do not exist, the function falls back to reading from the standard + /// key-values + /// + private static void GetLocalizedNamesByRegistryKey(RegistryKey key, out string? displayName, out string? standardName, out string? daylightName) + { + displayName = string.Empty; + standardName = string.Empty; + daylightName = string.Empty; + + // read the MUI_ registry keys + string? displayNameMuiResource = key.GetValue(MuiDisplayValue, string.Empty) as string; + string? standardNameMuiResource = key.GetValue(MuiStandardValue, string.Empty) as string; + string? daylightNameMuiResource = key.GetValue(MuiDaylightValue, string.Empty) as string; + + // try to load the strings from the native resource DLL(s) + if (!string.IsNullOrEmpty(displayNameMuiResource)) + { + displayName = TryGetLocalizedNameByMuiNativeResource(displayNameMuiResource); + } + + if (!string.IsNullOrEmpty(standardNameMuiResource)) + { + standardName = TryGetLocalizedNameByMuiNativeResource(standardNameMuiResource); + } + + if (!string.IsNullOrEmpty(daylightNameMuiResource)) + { + daylightName = TryGetLocalizedNameByMuiNativeResource(daylightNameMuiResource); + } + + // fallback to using the standard registry keys + if (string.IsNullOrEmpty(displayName)) + { + displayName = key.GetValue(DisplayValue, string.Empty) as string; + } + if (string.IsNullOrEmpty(standardName)) + { + standardName = key.GetValue(StandardValue, string.Empty) as string; + } + if (string.IsNullOrEmpty(daylightName)) + { + daylightName = key.GetValue(DaylightValue, string.Empty) as string; + } + } + + /// + /// Helper function that takes a string representing a time_zone_name registry key name + /// and returns a TimeZoneInfo instance. + /// + private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, out TimeZoneInfo? value, out Exception? e) + { + e = null; + + // Standard Time Zone Registry Data + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + // HKLM + // Software + // Microsoft + // Windows NT + // CurrentVersion + // Time Zones + // + // * STD, REG_SZ "Standard Time Name" + // (For OS installed zones, this will always be English) + // * MUI_STD, REG_SZ "@tzres.dll,-1234" + // Indirect string to localized resource for Standard Time, + // add "%windir%\system32\" after "@" + // * DLT, REG_SZ "Daylight Time Name" + // (For OS installed zones, this will always be English) + // * MUI_DLT, REG_SZ "@tzres.dll,-1234" + // Indirect string to localized resource for Daylight Time, + // add "%windir%\system32\" after "@" + // * Display, REG_SZ "Display Name like (GMT-8:00) Pacific Time..." + // * MUI_Display, REG_SZ "@tzres.dll,-1234" + // Indirect string to localized resource for the Display, + // add "%windir%\system32\" after "@" + // * TZI, REG_BINARY REG_TZI_FORMAT + // + using (RegistryKey? key = Registry.LocalMachine.OpenSubKey(TimeZonesRegistryHive + "\\" + id, writable: false)) + { + if (key == null) + { + value = null; + return TimeZoneInfoResult.TimeZoneNotFoundException; + } + + REG_TZI_FORMAT defaultTimeZoneInformation; + if (!TryGetTimeZoneEntryFromRegistry(key, TimeZoneInfoValue, out defaultTimeZoneInformation)) + { + // the registry value could not be cast to a byte array + value = null; + return TimeZoneInfoResult.InvalidTimeZoneException; + } + + AdjustmentRule[]? adjustmentRules; + if (!TryCreateAdjustmentRules(id, defaultTimeZoneInformation, out adjustmentRules, out e, defaultTimeZoneInformation.Bias)) + { + value = null; + return TimeZoneInfoResult.InvalidTimeZoneException; + } + + GetLocalizedNamesByRegistryKey(key, out string? displayName, out string? standardName, out string? daylightName); + + try + { + value = new TimeZoneInfo( + id, + new TimeSpan(0, -(defaultTimeZoneInformation.Bias), 0), + displayName, + standardName, + daylightName, + adjustmentRules, + disableDaylightSavingTime: false); + + return TimeZoneInfoResult.Success; + } + catch (ArgumentException ex) + { + // TimeZoneInfo constructor can throw ArgumentException and InvalidTimeZoneException + value = null; + e = ex; + return TimeZoneInfoResult.InvalidTimeZoneException; + } + catch (InvalidTimeZoneException ex) + { + // TimeZoneInfo constructor can throw ArgumentException and InvalidTimeZoneException + value = null; + e = ex; + return TimeZoneInfoResult.InvalidTimeZoneException; + } + } + } + } +} diff --git a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.cs b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.cs index 28775985e8bc..0783f9bdba2e 100644 --- a/src/System.Private.CoreLib/shared/System/TimeZoneInfo.cs +++ b/src/System.Private.CoreLib/shared/System/TimeZoneInfo.cs @@ -1,2060 +1,2060 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Globalization; -using System.Runtime.Serialization; -using System.Threading; - -namespace System -{ - // - // DateTime uses TimeZoneInfo under the hood for IsDaylightSavingTime, IsAmbiguousTime, and GetUtcOffset. - // These TimeZoneInfo APIs can throw ArgumentException when an Invalid-Time is passed in. To avoid this - // unwanted behavior in DateTime public APIs, DateTime internally passes the - // TimeZoneInfoOptions.NoThrowOnInvalidTime flag to internal TimeZoneInfo APIs. - // - // In the future we can consider exposing similar options on the public TimeZoneInfo APIs if there is enough - // demand for this alternate behavior. - // - [Flags] - internal enum TimeZoneInfoOptions - { - None = 1, - NoThrowOnInvalidTime = 2 - }; - - [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public sealed partial class TimeZoneInfo : IEquatable, ISerializable, IDeserializationCallback - { - private enum TimeZoneInfoResult - { - Success = 0, - TimeZoneNotFoundException = 1, - InvalidTimeZoneException = 2, - SecurityException = 3 - }; - - private readonly string _id; - private readonly string? _displayName; - private readonly string? _standardDisplayName; - private readonly string? _daylightDisplayName; - private readonly TimeSpan _baseUtcOffset; - private readonly bool _supportsDaylightSavingTime; - private readonly AdjustmentRule[]? _adjustmentRules; - - // constants for TimeZoneInfo.Local and TimeZoneInfo.Utc - private const string UtcId = "UTC"; - private const string LocalId = "Local"; - - private static readonly TimeZoneInfo s_utcTimeZone = CreateCustomTimeZone(UtcId, TimeSpan.Zero, UtcId, UtcId); - - private static CachedData s_cachedData = new CachedData(); - - // - // All cached data are encapsulated in a helper class to allow consistent view even when the data are refreshed using ClearCachedData() - // - // For example, TimeZoneInfo.Local can be cleared by another thread calling TimeZoneInfo.ClearCachedData. Without the consistent snapshot, - // there is a chance that the internal ConvertTime calls will throw since 'source' won't be reference equal to the new TimeZoneInfo.Local. - // - private sealed partial class CachedData - { - private volatile TimeZoneInfo? _localTimeZone; - - private TimeZoneInfo CreateLocal() - { - lock (this) - { - TimeZoneInfo? timeZone = _localTimeZone; - if (timeZone == null) - { - timeZone = GetLocalTimeZone(this); - - // this step is to break the reference equality - // between TimeZoneInfo.Local and a second time zone - // such as "Pacific Standard Time" - timeZone = new TimeZoneInfo( - timeZone._id, - timeZone._baseUtcOffset, - timeZone._displayName, - timeZone._standardDisplayName, - timeZone._daylightDisplayName, - timeZone._adjustmentRules, - disableDaylightSavingTime: false); - - _localTimeZone = timeZone; - } - return timeZone; - } - } - - public TimeZoneInfo Local - { - get - { - TimeZoneInfo? timeZone = _localTimeZone; - if (timeZone == null) - { - timeZone = CreateLocal(); - } - return timeZone; - } - } - - /// - /// Helper function that returns the corresponding DateTimeKind for this TimeZoneInfo. - /// - public DateTimeKind GetCorrespondingKind(TimeZoneInfo? timeZone) - { - // We check reference equality to see if 'this' is the same as - // TimeZoneInfo.Local or TimeZoneInfo.Utc. This check is needed to - // support setting the DateTime Kind property to 'Local' or - // 'Utc' on the ConverTime(...) return value. - // - // Using reference equality instead of value equality was a - // performance based design compromise. The reference equality - // has much greater performance, but it reduces the number of - // returned DateTime's that can be properly set as 'Local' or 'Utc'. - // - // For example, the user could be converting to the TimeZoneInfo returned - // by FindSystemTimeZoneById("Pacific Standard Time") and their local - // machine may be in Pacific time. If we used value equality to determine - // the corresponding Kind then this conversion would be tagged as 'Local'; - // where as we are currently tagging the returned DateTime as 'Unspecified' - // in this example. Only when the user passes in TimeZoneInfo.Local or - // TimeZoneInfo.Utc to the ConvertTime(...) methods will this check succeed. - // - return - ReferenceEquals(timeZone, s_utcTimeZone) ? DateTimeKind.Utc : - ReferenceEquals(timeZone, _localTimeZone) ? DateTimeKind.Local : - DateTimeKind.Unspecified; - } - - public Dictionary? _systemTimeZones; - public ReadOnlyCollection? _readOnlySystemTimeZones; - public bool _allSystemTimeZonesRead; - }; - - // used by GetUtcOffsetFromUtc (DateTime.Now, DateTime.ToLocalTime) for max/min whole-day range checks - private static readonly DateTime s_maxDateOnly = new DateTime(9999, 12, 31); - private static readonly DateTime s_minDateOnly = new DateTime(1, 1, 2); - - public string Id => _id; - - public string DisplayName => _displayName ?? string.Empty; - - public string StandardName => _standardDisplayName ?? string.Empty; - - public string DaylightName => _daylightDisplayName ?? string.Empty; - - public TimeSpan BaseUtcOffset => _baseUtcOffset; - - public bool SupportsDaylightSavingTime => _supportsDaylightSavingTime; - - /// - /// Returns an array of TimeSpan objects representing all of - /// possible UTC offset values for this ambiguous time. - /// - public TimeSpan[] GetAmbiguousTimeOffsets(DateTimeOffset dateTimeOffset) - { - if (!SupportsDaylightSavingTime) - { - throw new ArgumentException(SR.Argument_DateTimeOffsetIsNotAmbiguous, nameof(dateTimeOffset)); - } - - DateTime adjustedTime = ConvertTime(dateTimeOffset, this).DateTime; - - bool isAmbiguous = false; - int? ruleIndex; - AdjustmentRule? rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime, out ruleIndex); - if (rule != null && rule.HasDaylightSaving) - { - DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); - isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime); - } - - if (!isAmbiguous) - { - throw new ArgumentException(SR.Argument_DateTimeOffsetIsNotAmbiguous, nameof(dateTimeOffset)); - } - - // the passed in dateTime is ambiguous in this TimeZoneInfo instance - TimeSpan[] timeSpans = new TimeSpan[2]; - - TimeSpan actualUtcOffset = _baseUtcOffset + rule!.BaseUtcOffsetDelta; - - // the TimeSpan array must be sorted from least to greatest - if (rule.DaylightDelta > TimeSpan.Zero) - { - timeSpans[0] = actualUtcOffset; // FUTURE: + rule.StandardDelta; - timeSpans[1] = actualUtcOffset + rule.DaylightDelta; - } - else - { - timeSpans[0] = actualUtcOffset + rule.DaylightDelta; - timeSpans[1] = actualUtcOffset; // FUTURE: + rule.StandardDelta; - } - return timeSpans; - } - - /// - /// Returns an array of TimeSpan objects representing all of - /// possible UTC offset values for this ambiguous time. - /// - public TimeSpan[] GetAmbiguousTimeOffsets(DateTime dateTime) - { - if (!SupportsDaylightSavingTime) - { - throw new ArgumentException(SR.Argument_DateTimeIsNotAmbiguous, nameof(dateTime)); - } - - DateTime adjustedTime; - if (dateTime.Kind == DateTimeKind.Local) - { - CachedData cachedData = s_cachedData; - adjustedTime = ConvertTime(dateTime, cachedData.Local, this, TimeZoneInfoOptions.None, cachedData); - } - else if (dateTime.Kind == DateTimeKind.Utc) - { - CachedData cachedData = s_cachedData; - adjustedTime = ConvertTime(dateTime, s_utcTimeZone, this, TimeZoneInfoOptions.None, cachedData); - } - else - { - adjustedTime = dateTime; - } - - bool isAmbiguous = false; - int? ruleIndex; - AdjustmentRule? rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime, out ruleIndex); - if (rule != null && rule.HasDaylightSaving) - { - DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); - isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime); - } - - if (!isAmbiguous) - { - throw new ArgumentException(SR.Argument_DateTimeIsNotAmbiguous, nameof(dateTime)); - } - - // the passed in dateTime is ambiguous in this TimeZoneInfo instance - TimeSpan[] timeSpans = new TimeSpan[2]; - TimeSpan actualUtcOffset = _baseUtcOffset + rule!.BaseUtcOffsetDelta; - - // the TimeSpan array must be sorted from least to greatest - if (rule.DaylightDelta > TimeSpan.Zero) - { - timeSpans[0] = actualUtcOffset; // FUTURE: + rule.StandardDelta; - timeSpans[1] = actualUtcOffset + rule.DaylightDelta; - } - else - { - timeSpans[0] = actualUtcOffset + rule.DaylightDelta; - timeSpans[1] = actualUtcOffset; // FUTURE: + rule.StandardDelta; - } - return timeSpans; - } - - // note the time is already adjusted - private AdjustmentRule? GetAdjustmentRuleForAmbiguousOffsets(DateTime adjustedTime, out int? ruleIndex) - { - AdjustmentRule? rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex); - if (rule != null && rule.NoDaylightTransitions && !rule.HasDaylightSaving) - { - // When using NoDaylightTransitions rules, each rule is only for one offset. - // When looking for the Daylight savings rules, and we found the non-DST rule, - // then we get the rule right before this rule. - return GetPreviousAdjustmentRule(rule, ruleIndex); - } - - return rule; - } - - /// - /// Gets the AdjustmentRule that is immediately preceding the specified rule. - /// If the specified rule is the first AdjustmentRule, or it isn't in _adjustmentRules, - /// then the specified rule is returned. - /// - private AdjustmentRule GetPreviousAdjustmentRule(AdjustmentRule rule, int? ruleIndex) - { - Debug.Assert(rule.NoDaylightTransitions, "GetPreviousAdjustmentRule should only be used with NoDaylightTransitions rules."); - Debug.Assert(_adjustmentRules != null); - - if (ruleIndex.HasValue && 0 < ruleIndex.GetValueOrDefault() && ruleIndex.GetValueOrDefault() < _adjustmentRules.Length) - { - return _adjustmentRules[ruleIndex.GetValueOrDefault() - 1]; - } - - AdjustmentRule result = rule; - for (int i = 1; i < _adjustmentRules.Length; i++) - { - // use ReferenceEquals here instead of AdjustmentRule.Equals because - // ReferenceEquals is much faster. This is safe because all the callers - // of GetPreviousAdjustmentRule pass in a rule that was retrieved from - // _adjustmentRules. A different approach will be needed if this ever changes. - if (ReferenceEquals(rule, _adjustmentRules[i])) - { - result = _adjustmentRules[i - 1]; - break; - } - } - return result; - } - - /// - /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance. - /// - public TimeSpan GetUtcOffset(DateTimeOffset dateTimeOffset) => - GetUtcOffsetFromUtc(dateTimeOffset.UtcDateTime, this); - - /// - /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance. - /// - public TimeSpan GetUtcOffset(DateTime dateTime) => - GetUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime, s_cachedData); - - // Shortcut for TimeZoneInfo.Local.GetUtcOffset - internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) - { - CachedData cachedData = s_cachedData; - return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); - } - - /// - /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance. - /// - internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) => - GetUtcOffset(dateTime, flags, s_cachedData); - - private TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags, CachedData cachedData) - { - if (dateTime.Kind == DateTimeKind.Local) - { - if (cachedData.GetCorrespondingKind(this) != DateTimeKind.Local) - { - // - // normal case of converting from Local to Utc and then getting the offset from the UTC DateTime - // - DateTime adjustedTime = ConvertTime(dateTime, cachedData.Local, s_utcTimeZone, flags); - return GetUtcOffsetFromUtc(adjustedTime, this); - } - - // - // Fall through for TimeZoneInfo.Local.GetUtcOffset(date) - // to handle an edge case with Invalid-Times for DateTime formatting: - // - // Consider the invalid PST time "2007-03-11T02:00:00.0000000-08:00" - // - // By directly calling GetUtcOffset instead of converting to UTC and then calling GetUtcOffsetFromUtc - // the correct invalid offset of "-08:00" is returned. In the normal case of converting to UTC as an - // interim-step, the invalid time is adjusted into a *valid* UTC time which causes a change in output: - // - // 1) invalid PST time "2007-03-11T02:00:00.0000000-08:00" - // 2) converted to UTC "2007-03-11T10:00:00.0000000Z" - // 3) offset returned "2007-03-11T03:00:00.0000000-07:00" - // - } - else if (dateTime.Kind == DateTimeKind.Utc) - { - if (cachedData.GetCorrespondingKind(this) == DateTimeKind.Utc) - { - return _baseUtcOffset; - } - else - { - // - // passing in a UTC dateTime to a non-UTC TimeZoneInfo instance is a - // special Loss-Less case. - // - return GetUtcOffsetFromUtc(dateTime, this); - } - } - - return GetUtcOffset(dateTime, this, flags); - } - - /// - /// Returns true if the time is during the ambiguous time period - /// for the current TimeZoneInfo instance. - /// - public bool IsAmbiguousTime(DateTimeOffset dateTimeOffset) - { - if (!_supportsDaylightSavingTime) - { - return false; - } - - DateTimeOffset adjustedTime = ConvertTime(dateTimeOffset, this); - return IsAmbiguousTime(adjustedTime.DateTime); - } - - /// - /// Returns true if the time is during the ambiguous time period - /// for the current TimeZoneInfo instance. - /// - public bool IsAmbiguousTime(DateTime dateTime) => - IsAmbiguousTime(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime); - - /// - /// Returns true if the time is during the ambiguous time period - /// for the current TimeZoneInfo instance. - /// - internal bool IsAmbiguousTime(DateTime dateTime, TimeZoneInfoOptions flags) - { - if (!_supportsDaylightSavingTime) - { - return false; - } - - CachedData cachedData = s_cachedData; - DateTime adjustedTime = - dateTime.Kind == DateTimeKind.Local ? ConvertTime(dateTime, cachedData.Local, this, flags, cachedData) : - dateTime.Kind == DateTimeKind.Utc ? ConvertTime(dateTime, s_utcTimeZone, this, flags, cachedData) : - dateTime; - - int? ruleIndex; - AdjustmentRule? rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex); - if (rule != null && rule.HasDaylightSaving) - { - DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); - return GetIsAmbiguousTime(adjustedTime, rule, daylightTime); - } - return false; - } - - /// - /// Returns true if the time is during Daylight Saving time for the current TimeZoneInfo instance. - /// - public bool IsDaylightSavingTime(DateTimeOffset dateTimeOffset) - { - bool isDaylightSavingTime; - GetUtcOffsetFromUtc(dateTimeOffset.UtcDateTime, this, out isDaylightSavingTime); - return isDaylightSavingTime; - } - - /// - /// Returns true if the time is during Daylight Saving time for the current TimeZoneInfo instance. - /// - public bool IsDaylightSavingTime(DateTime dateTime) => - IsDaylightSavingTime(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime, s_cachedData); - - /// - /// Returns true if the time is during Daylight Saving time for the current TimeZoneInfo instance. - /// - internal bool IsDaylightSavingTime(DateTime dateTime, TimeZoneInfoOptions flags) => - IsDaylightSavingTime(dateTime, flags, s_cachedData); - - private bool IsDaylightSavingTime(DateTime dateTime, TimeZoneInfoOptions flags, CachedData cachedData) - { - // - // dateTime.Kind is UTC, then time will be converted from UTC - // into current instance's timezone - // dateTime.Kind is Local, then time will be converted from Local - // into current instance's timezone - // dateTime.Kind is UnSpecified, then time is already in - // current instance's timezone - // - // Our DateTime handles ambiguous times, (one is in the daylight and - // one is in standard.) If a new DateTime is constructed during ambiguous - // time, it is defaulted to "Standard" (i.e. this will return false). - // For Invalid times, we will return false - - if (!_supportsDaylightSavingTime || _adjustmentRules == null) - { - return false; - } - - DateTime adjustedTime; - // - // handle any Local/Utc special cases... - // - if (dateTime.Kind == DateTimeKind.Local) - { - adjustedTime = ConvertTime(dateTime, cachedData.Local, this, flags, cachedData); - } - else if (dateTime.Kind == DateTimeKind.Utc) - { - if (cachedData.GetCorrespondingKind(this) == DateTimeKind.Utc) - { - // simple always false case: TimeZoneInfo.Utc.IsDaylightSavingTime(dateTime, flags); - return false; - } - else - { - // - // passing in a UTC dateTime to a non-UTC TimeZoneInfo instance is a - // special Loss-Less case. - // - bool isDaylightSavings; - GetUtcOffsetFromUtc(dateTime, this, out isDaylightSavings); - return isDaylightSavings; - } - } - else - { - adjustedTime = dateTime; - } - - // - // handle the normal cases... - // - int? ruleIndex; - AdjustmentRule? rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex); - if (rule != null && rule.HasDaylightSaving) - { - DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); - return GetIsDaylightSavings(adjustedTime, rule, daylightTime, flags); - } - else - { - return false; - } - } - - /// - /// Returns true when dateTime falls into a "hole in time". - /// - public bool IsInvalidTime(DateTime dateTime) - { - bool isInvalid = false; - - if ((dateTime.Kind == DateTimeKind.Unspecified) || - (dateTime.Kind == DateTimeKind.Local && s_cachedData.GetCorrespondingKind(this) == DateTimeKind.Local)) - { - // only check Unspecified and (Local when this TimeZoneInfo instance is Local) - int? ruleIndex; - AdjustmentRule? rule = GetAdjustmentRuleForTime(dateTime, out ruleIndex); - - if (rule != null && rule.HasDaylightSaving) - { - DaylightTimeStruct daylightTime = GetDaylightTime(dateTime.Year, rule, ruleIndex); - isInvalid = GetIsInvalidTime(dateTime, rule, daylightTime); - } - else - { - isInvalid = false; - } - } - - return isInvalid; - } - - /// - /// Clears data from static members. - /// - public static void ClearCachedData() - { - // Clear a fresh instance of cached data - s_cachedData = new CachedData(); - } - - /// - /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone. - /// - public static DateTimeOffset ConvertTimeBySystemTimeZoneId(DateTimeOffset dateTimeOffset, string destinationTimeZoneId) => - ConvertTime(dateTimeOffset, FindSystemTimeZoneById(destinationTimeZoneId)); - - /// - /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone. - /// - public static DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, string destinationTimeZoneId) => - ConvertTime(dateTime, FindSystemTimeZoneById(destinationTimeZoneId)); - - /// - /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone. - /// - public static DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId) - { - if (dateTime.Kind == DateTimeKind.Local && string.Equals(sourceTimeZoneId, Local.Id, StringComparison.OrdinalIgnoreCase)) - { - // TimeZoneInfo.Local can be cleared by another thread calling TimeZoneInfo.ClearCachedData. - // Take snapshot of cached data to guarantee this method will not be impacted by the ClearCachedData call. - // Without the snapshot, there is a chance that ConvertTime will throw since 'source' won't - // be reference equal to the new TimeZoneInfo.Local - // - CachedData cachedData = s_cachedData; - return ConvertTime(dateTime, cachedData.Local, FindSystemTimeZoneById(destinationTimeZoneId), TimeZoneInfoOptions.None, cachedData); - } - else if (dateTime.Kind == DateTimeKind.Utc && string.Equals(sourceTimeZoneId, Utc.Id, StringComparison.OrdinalIgnoreCase)) - { - return ConvertTime(dateTime, s_utcTimeZone, FindSystemTimeZoneById(destinationTimeZoneId), TimeZoneInfoOptions.None, s_cachedData); - } - else - { - return ConvertTime(dateTime, FindSystemTimeZoneById(sourceTimeZoneId), FindSystemTimeZoneById(destinationTimeZoneId)); - } - } - - /// - /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone - /// - public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone) - { - if (destinationTimeZone == null) - { - throw new ArgumentNullException(nameof(destinationTimeZone)); - } - - // calculate the destination time zone offset - DateTime utcDateTime = dateTimeOffset.UtcDateTime; - TimeSpan destinationOffset = GetUtcOffsetFromUtc(utcDateTime, destinationTimeZone); - - // check for overflow - long ticks = utcDateTime.Ticks + destinationOffset.Ticks; - - return - ticks > DateTimeOffset.MaxValue.Ticks ? DateTimeOffset.MaxValue : - ticks < DateTimeOffset.MinValue.Ticks ? DateTimeOffset.MinValue : - new DateTimeOffset(ticks, destinationOffset); - } - - /// - /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone - /// - public static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo destinationTimeZone) - { - if (destinationTimeZone == null) - { - throw new ArgumentNullException(nameof(destinationTimeZone)); - } - - // Special case to give a way clearing the cache without exposing ClearCachedData() - if (dateTime.Ticks == 0) - { - ClearCachedData(); - } - CachedData cachedData = s_cachedData; - TimeZoneInfo sourceTimeZone = dateTime.Kind == DateTimeKind.Utc ? s_utcTimeZone : cachedData.Local; - return ConvertTime(dateTime, sourceTimeZone, destinationTimeZone, TimeZoneInfoOptions.None, cachedData); - } - - /// - /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone - /// - public static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone) => - ConvertTime(dateTime, sourceTimeZone, destinationTimeZone, TimeZoneInfoOptions.None, s_cachedData); - - /// - /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone - /// - internal static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone, TimeZoneInfoOptions flags) => - ConvertTime(dateTime, sourceTimeZone, destinationTimeZone, flags, s_cachedData); - - private static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone, TimeZoneInfoOptions flags, CachedData cachedData) - { - if (sourceTimeZone == null) - { - throw new ArgumentNullException(nameof(sourceTimeZone)); - } - - if (destinationTimeZone == null) - { - throw new ArgumentNullException(nameof(destinationTimeZone)); - } - - DateTimeKind sourceKind = cachedData.GetCorrespondingKind(sourceTimeZone); - if (((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) && (dateTime.Kind != DateTimeKind.Unspecified) && (dateTime.Kind != sourceKind)) - { - throw new ArgumentException(SR.Argument_ConvertMismatch, nameof(sourceTimeZone)); - } - - // - // check to see if the DateTime is in an invalid time range. This check - // requires the current AdjustmentRule and DaylightTime - which are also - // needed to calculate 'sourceOffset' in the normal conversion case. - // By calculating the 'sourceOffset' here we improve the - // performance for the normal case at the expense of the 'ArgumentException' - // case and Loss-less Local special cases. - // - int? sourceRuleIndex; - AdjustmentRule? sourceRule = sourceTimeZone.GetAdjustmentRuleForTime(dateTime, out sourceRuleIndex); - TimeSpan sourceOffset = sourceTimeZone.BaseUtcOffset; - - if (sourceRule != null) - { - sourceOffset = sourceOffset + sourceRule.BaseUtcOffsetDelta; - if (sourceRule.HasDaylightSaving) - { - bool sourceIsDaylightSavings = false; - DaylightTimeStruct sourceDaylightTime = sourceTimeZone.GetDaylightTime(dateTime.Year, sourceRule, sourceRuleIndex); - - // 'dateTime' might be in an invalid time range since it is in an AdjustmentRule - // period that supports DST - if (((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) && GetIsInvalidTime(dateTime, sourceRule, sourceDaylightTime)) - { - throw new ArgumentException(SR.Argument_DateTimeIsInvalid, nameof(dateTime)); - } - sourceIsDaylightSavings = GetIsDaylightSavings(dateTime, sourceRule, sourceDaylightTime, flags); - - // adjust the sourceOffset according to the Adjustment Rule / Daylight Saving Rule - sourceOffset += (sourceIsDaylightSavings ? sourceRule.DaylightDelta : TimeSpan.Zero /*FUTURE: sourceRule.StandardDelta*/); - } - } - - DateTimeKind targetKind = cachedData.GetCorrespondingKind(destinationTimeZone); - - // handle the special case of Loss-less Local->Local and UTC->UTC) - if (dateTime.Kind != DateTimeKind.Unspecified && sourceKind != DateTimeKind.Unspecified && sourceKind == targetKind) - { - return dateTime; - } - - long utcTicks = dateTime.Ticks - sourceOffset.Ticks; - - // handle the normal case by converting from 'source' to UTC and then to 'target' - bool isAmbiguousLocalDst; - DateTime targetConverted = ConvertUtcToTimeZone(utcTicks, destinationTimeZone, out isAmbiguousLocalDst); - - if (targetKind == DateTimeKind.Local) - { - // Because the ticks conversion between UTC and local is lossy, we need to capture whether the - // time is in a repeated hour so that it can be passed to the DateTime constructor. - return new DateTime(targetConverted.Ticks, DateTimeKind.Local, isAmbiguousLocalDst); - } - else - { - return new DateTime(targetConverted.Ticks, targetKind); - } - } - - /// - /// Converts the value of a DateTime object from Coordinated Universal Time (UTC) to the destinationTimeZone. - /// - public static DateTime ConvertTimeFromUtc(DateTime dateTime, TimeZoneInfo destinationTimeZone) => - ConvertTime(dateTime, s_utcTimeZone, destinationTimeZone, TimeZoneInfoOptions.None, s_cachedData); - - /// - /// Converts the value of a DateTime object to Coordinated Universal Time (UTC). - /// - public static DateTime ConvertTimeToUtc(DateTime dateTime) - { - if (dateTime.Kind == DateTimeKind.Utc) - { - return dateTime; - } - CachedData cachedData = s_cachedData; - return ConvertTime(dateTime, cachedData.Local, s_utcTimeZone, TimeZoneInfoOptions.None, cachedData); - } - - /// - /// Converts the value of a DateTime object to Coordinated Universal Time (UTC). - /// - internal static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags) - { - if (dateTime.Kind == DateTimeKind.Utc) - { - return dateTime; - } - CachedData cachedData = s_cachedData; - return ConvertTime(dateTime, cachedData.Local, s_utcTimeZone, flags, cachedData); - } - - /// - /// Converts the value of a DateTime object to Coordinated Universal Time (UTC). - /// - public static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfo sourceTimeZone) => - ConvertTime(dateTime, sourceTimeZone, s_utcTimeZone, TimeZoneInfoOptions.None, s_cachedData); - - /// - /// Returns value equality. Equals does not compare any localizable - /// String objects (DisplayName, StandardName, DaylightName). - /// - public bool Equals(TimeZoneInfo? other) => - other != null && - string.Equals(_id, other._id, StringComparison.OrdinalIgnoreCase) && - HasSameRules(other); - - public override bool Equals(object? obj) => Equals(obj as TimeZoneInfo); - - public static TimeZoneInfo FromSerializedString(string source) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - if (source.Length == 0) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidSerializedString, source), nameof(source)); - } - - return StringSerializer.GetDeserializedTimeZoneInfo(source); - } - - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(_id); - - /// - /// Returns a containing all valid TimeZone's - /// from the local machine. The entries in the collection are sorted by - /// . - /// This method does *not* throw TimeZoneNotFoundException or InvalidTimeZoneException. - /// - public static ReadOnlyCollection GetSystemTimeZones() - { - CachedData cachedData = s_cachedData; - - lock (cachedData) - { - if (cachedData._readOnlySystemTimeZones == null) - { - PopulateAllSystemTimeZones(cachedData); - cachedData._allSystemTimeZonesRead = true; - - List list; - if (cachedData._systemTimeZones != null) - { - // return a collection of the cached system time zones - list = new List(cachedData._systemTimeZones.Values); - } - else - { - // return an empty collection - list = new List(); - } - - // sort and copy the TimeZoneInfo's into a ReadOnlyCollection for the user - list.Sort((x, y) => - { - // sort by BaseUtcOffset first and by DisplayName second - this is similar to the Windows Date/Time control panel - int comparison = x.BaseUtcOffset.CompareTo(y.BaseUtcOffset); - return comparison == 0 ? string.CompareOrdinal(x.DisplayName, y.DisplayName) : comparison; - }); - - cachedData._readOnlySystemTimeZones = new ReadOnlyCollection(list); - } - } - return cachedData._readOnlySystemTimeZones; - } - - /// - /// Value equality on the "adjustmentRules" array - /// - public bool HasSameRules(TimeZoneInfo other) - { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } - - // check the utcOffset and supportsDaylightSavingTime members - if (_baseUtcOffset != other._baseUtcOffset || - _supportsDaylightSavingTime != other._supportsDaylightSavingTime) - { - return false; - } - - bool sameRules; - AdjustmentRule[]? currentRules = _adjustmentRules; - AdjustmentRule[]? otherRules = other._adjustmentRules; - - sameRules = - (currentRules == null && otherRules == null) || - (currentRules != null && otherRules != null); - - if (!sameRules) - { - // AdjustmentRule array mismatch - return false; - } - - if (currentRules != null) - { - if (currentRules.Length != otherRules!.Length) - { - // AdjustmentRule array length mismatch - return false; - } - - for (int i = 0; i < currentRules.Length; i++) - { - if (!(currentRules[i]).Equals(otherRules[i])) - { - // AdjustmentRule value-equality mismatch - return false; - } - } - } - return sameRules; - } - - /// - /// Returns a TimeZoneInfo instance that represents the local time on the machine. - /// Accessing this property may throw InvalidTimeZoneException or COMException - /// if the machine is in an unstable or corrupt state. - /// - public static TimeZoneInfo Local => s_cachedData.Local; - - // - // ToSerializedString - - // - // "TimeZoneInfo" := TimeZoneInfo Data;[AdjustmentRule Data 1];...;[AdjustmentRule Data N] - // - // "TimeZoneInfo Data" := <_id>;<_baseUtcOffset>;<_displayName>; - // <_standardDisplayName>;<_daylightDispayName>; - // - // "AdjustmentRule Data" := ;;; - // [TransitionTime Data DST Start] - // [TransitionTime Data DST End] - // - // "TransitionTime Data" += ;;;; - // - public string ToSerializedString() => StringSerializer.GetSerializedString(this); - - /// - /// Returns the : "(GMT-08:00) Pacific Time (US & Canada); Tijuana" - /// - public override string ToString() => DisplayName; - - /// - /// Returns a TimeZoneInfo instance that represents Universal Coordinated Time (UTC) - /// - public static TimeZoneInfo Utc => s_utcTimeZone; - - private TimeZoneInfo( - string id, - TimeSpan baseUtcOffset, - string? displayName, - string? standardDisplayName, - string? daylightDisplayName, - AdjustmentRule[]? adjustmentRules, - bool disableDaylightSavingTime) - { - bool adjustmentRulesSupportDst; - ValidateTimeZoneInfo(id, baseUtcOffset, adjustmentRules, out adjustmentRulesSupportDst); - - _id = id; - _baseUtcOffset = baseUtcOffset; - _displayName = displayName; - _standardDisplayName = standardDisplayName; - _daylightDisplayName = disableDaylightSavingTime ? null : daylightDisplayName; - _supportsDaylightSavingTime = adjustmentRulesSupportDst && !disableDaylightSavingTime; - _adjustmentRules = adjustmentRules; - } - - /// - /// Returns a simple TimeZoneInfo instance that does not support Daylight Saving Time. - /// - public static TimeZoneInfo CreateCustomTimeZone( - string id, - TimeSpan baseUtcOffset, - string? displayName, - string? standardDisplayName) - { - return new TimeZoneInfo( - id, - baseUtcOffset, - displayName, - standardDisplayName, - standardDisplayName, - adjustmentRules: null, - disableDaylightSavingTime: false); - } - - /// - /// Returns a TimeZoneInfo instance that may support Daylight Saving Time. - /// - public static TimeZoneInfo CreateCustomTimeZone( - string id, - TimeSpan baseUtcOffset, - string? displayName, - string? standardDisplayName, - string? daylightDisplayName, - AdjustmentRule[]? adjustmentRules) - { - return CreateCustomTimeZone( - id, - baseUtcOffset, - displayName, - standardDisplayName, - daylightDisplayName, - adjustmentRules, - disableDaylightSavingTime: false); - } - - /// - /// Returns a TimeZoneInfo instance that may support Daylight Saving Time. - /// - public static TimeZoneInfo CreateCustomTimeZone( - string id, - TimeSpan baseUtcOffset, - string? displayName, - string? standardDisplayName, - string? daylightDisplayName, - AdjustmentRule[]? adjustmentRules, - bool disableDaylightSavingTime) - { - if (!disableDaylightSavingTime && adjustmentRules?.Length > 0) - { - adjustmentRules = (AdjustmentRule[])adjustmentRules.Clone(); - } - - return new TimeZoneInfo( - id, - baseUtcOffset, - displayName, - standardDisplayName, - daylightDisplayName, - adjustmentRules, - disableDaylightSavingTime); - } - - void IDeserializationCallback.OnDeserialization(object sender) - { - try - { - bool adjustmentRulesSupportDst; - ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out adjustmentRulesSupportDst); - - if (adjustmentRulesSupportDst != _supportsDaylightSavingTime) - { - throw new SerializationException(SR.Format(SR.Serialization_CorruptField, "SupportsDaylightSavingTime")); - } - } - catch (ArgumentException e) - { - throw new SerializationException(SR.Serialization_InvalidData, e); - } - catch (InvalidTimeZoneException e) - { - throw new SerializationException(SR.Serialization_InvalidData, e); - } - } - - void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - info.AddValue("Id", _id); // Do not rename (binary serialization) - info.AddValue("DisplayName", _displayName); // Do not rename (binary serialization) - info.AddValue("StandardName", _standardDisplayName); // Do not rename (binary serialization) - info.AddValue("DaylightName", _daylightDisplayName); // Do not rename (binary serialization) - info.AddValue("BaseUtcOffset", _baseUtcOffset); // Do not rename (binary serialization) - info.AddValue("AdjustmentRules", _adjustmentRules); // Do not rename (binary serialization) - info.AddValue("SupportsDaylightSavingTime", _supportsDaylightSavingTime); // Do not rename (binary serialization) - } - - private TimeZoneInfo(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } - - _id = (string)info.GetValue("Id", typeof(string))!; // Do not rename (binary serialization) - _displayName = (string?)info.GetValue("DisplayName", typeof(string)); // Do not rename (binary serialization) - _standardDisplayName = (string?)info.GetValue("StandardName", typeof(string)); // Do not rename (binary serialization) - _daylightDisplayName = (string?)info.GetValue("DaylightName", typeof(string)); // Do not rename (binary serialization) - _baseUtcOffset = (TimeSpan)info.GetValue("BaseUtcOffset", typeof(TimeSpan))!; // Do not rename (binary serialization) - _adjustmentRules = (AdjustmentRule[]?)info.GetValue("AdjustmentRules", typeof(AdjustmentRule[])); // Do not rename (binary serialization) - _supportsDaylightSavingTime = (bool)info.GetValue("SupportsDaylightSavingTime", typeof(bool))!; // Do not rename (binary serialization) - } - - private AdjustmentRule? GetAdjustmentRuleForTime(DateTime dateTime, out int? ruleIndex) - { - AdjustmentRule? result = GetAdjustmentRuleForTime(dateTime, dateTimeisUtc: false, ruleIndex: out ruleIndex); - Debug.Assert(result == null || ruleIndex.HasValue, "If an AdjustmentRule was found, ruleIndex should also be set."); - - return result; - } - - private AdjustmentRule? GetAdjustmentRuleForTime(DateTime dateTime, bool dateTimeisUtc, out int? ruleIndex) - { - if (_adjustmentRules == null || _adjustmentRules.Length == 0) - { - ruleIndex = null; - return null; - } - - // Only check the whole-date portion of the dateTime for DateTimeKind.Unspecified rules - - // This is because the AdjustmentRule DateStart & DateEnd are stored as - // Date-only values {4/2/2006 - 10/28/2006} but actually represent the - // time span {4/2/2006@00:00:00.00000 - 10/28/2006@23:59:59.99999} - DateTime date = dateTimeisUtc ? - (dateTime + BaseUtcOffset).Date : - dateTime.Date; - - int low = 0; - int high = _adjustmentRules.Length - 1; - - while (low <= high) - { - int median = low + ((high - low) >> 1); - - AdjustmentRule rule = _adjustmentRules[median]; - AdjustmentRule previousRule = median > 0 ? _adjustmentRules[median - 1] : rule; - - int compareResult = CompareAdjustmentRuleToDateTime(rule, previousRule, dateTime, date, dateTimeisUtc); - if (compareResult == 0) - { - ruleIndex = median; - return rule; - } - else if (compareResult < 0) - { - low = median + 1; - } - else - { - high = median - 1; - } - } - - ruleIndex = null; - return null; - } - - /// - /// Determines if 'rule' is the correct AdjustmentRule for the given dateTime. - /// - /// - /// A value less than zero if rule is for times before dateTime. - /// Zero if rule is correct for dateTime. - /// A value greater than zero if rule is for times after dateTime. - /// - private int CompareAdjustmentRuleToDateTime(AdjustmentRule rule, AdjustmentRule previousRule, - DateTime dateTime, DateTime dateOnly, bool dateTimeisUtc) - { - bool isAfterStart; - if (rule.DateStart.Kind == DateTimeKind.Utc) - { - DateTime dateTimeToCompare = dateTimeisUtc ? - dateTime : - // use the previous rule to compute the dateTimeToCompare, since the time daylight savings "switches" - // is based on the previous rule's offset - ConvertToUtc(dateTime, previousRule.DaylightDelta, previousRule.BaseUtcOffsetDelta); - - isAfterStart = dateTimeToCompare >= rule.DateStart; - } - else - { - // if the rule's DateStart is Unspecified, then use the whole-date portion - isAfterStart = dateOnly >= rule.DateStart; - } - - if (!isAfterStart) - { - return 1; - } - - bool isBeforeEnd; - if (rule.DateEnd.Kind == DateTimeKind.Utc) - { - DateTime dateTimeToCompare = dateTimeisUtc ? - dateTime : - ConvertToUtc(dateTime, rule.DaylightDelta, rule.BaseUtcOffsetDelta); - - isBeforeEnd = dateTimeToCompare <= rule.DateEnd; - } - else - { - // if the rule's DateEnd is Unspecified, then use the whole-date portion - isBeforeEnd = dateOnly <= rule.DateEnd; - } - - return isBeforeEnd ? 0 : -1; - } - - /// - /// Converts the dateTime to UTC using the specified deltas. - /// - private DateTime ConvertToUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta) => - ConvertToFromUtc(dateTime, daylightDelta, baseUtcOffsetDelta, convertToUtc: true); - - /// - /// Converts the dateTime from UTC using the specified deltas. - /// - private DateTime ConvertFromUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta) => - ConvertToFromUtc(dateTime, daylightDelta, baseUtcOffsetDelta, convertToUtc: false); - - /// - /// Converts the dateTime to or from UTC using the specified deltas. - /// - private DateTime ConvertToFromUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta, bool convertToUtc) - { - TimeSpan offset = BaseUtcOffset + daylightDelta + baseUtcOffsetDelta; - if (convertToUtc) - { - offset = offset.Negate(); - } - - long ticks = dateTime.Ticks + offset.Ticks; - - return - ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue : - ticks < DateTime.MinValue.Ticks ? DateTime.MinValue : - new DateTime(ticks); - } - - /// - /// Helper function that converts a dateTime from UTC into the destinationTimeZone - /// - Returns DateTime.MaxValue when the converted value is too large. - /// - Returns DateTime.MinValue when the converted value is too small. - /// - private static DateTime ConvertUtcToTimeZone(long ticks, TimeZoneInfo destinationTimeZone, out bool isAmbiguousLocalDst) - { - // used to calculate the UTC offset in the destinationTimeZone - DateTime utcConverted = - ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue : - ticks < DateTime.MinValue.Ticks ? DateTime.MinValue : - new DateTime(ticks); - - // verify the time is between MinValue and MaxValue in the new time zone - TimeSpan offset = GetUtcOffsetFromUtc(utcConverted, destinationTimeZone, out isAmbiguousLocalDst); - ticks += offset.Ticks; - - return - ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue : - ticks < DateTime.MinValue.Ticks ? DateTime.MinValue : - new DateTime(ticks); - } - - /// - /// Helper function that returns a DaylightTime from a year and AdjustmentRule. - /// - private DaylightTimeStruct GetDaylightTime(int year, AdjustmentRule rule, int? ruleIndex) - { - TimeSpan delta = rule.DaylightDelta; - DateTime startTime; - DateTime endTime; - if (rule.NoDaylightTransitions) - { - // NoDaylightTransitions rules don't use DaylightTransition Start and End, instead - // the DateStart and DateEnd are UTC times that represent when daylight savings time changes. - // Convert the UTC times into adjusted time zone times. - - // use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule - AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule, ruleIndex); - startTime = ConvertFromUtc(rule.DateStart, previousRule.DaylightDelta, previousRule.BaseUtcOffsetDelta); - - endTime = ConvertFromUtc(rule.DateEnd, rule.DaylightDelta, rule.BaseUtcOffsetDelta); - } - else - { - startTime = TransitionTimeToDateTime(year, rule.DaylightTransitionStart); - endTime = TransitionTimeToDateTime(year, rule.DaylightTransitionEnd); - } - return new DaylightTimeStruct(startTime, endTime, delta); - } - - /// - /// Helper function that checks if a given dateTime is in Daylight Saving Time (DST). - /// This function assumes the dateTime and AdjustmentRule are both in the same time zone. - /// - private static bool GetIsDaylightSavings(DateTime time, AdjustmentRule rule, DaylightTimeStruct daylightTime, TimeZoneInfoOptions flags) - { - if (rule == null) - { - return false; - } - - DateTime startTime; - DateTime endTime; - - if (time.Kind == DateTimeKind.Local) - { - // startTime and endTime represent the period from either the start of - // DST to the end and ***includes*** the potentially overlapped times - startTime = rule.IsStartDateMarkerForBeginningOfYear() ? - new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) : - daylightTime.Start + daylightTime.Delta; - - endTime = rule.IsEndDateMarkerForEndOfYear() ? - new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) : - daylightTime.End; - } - else - { - // startTime and endTime represent the period from either the start of DST to the end and - // ***does not include*** the potentially overlapped times - // - // -=-=-=-=-=- Pacific Standard Time -=-=-=-=-=-=- - // April 2, 2006 October 29, 2006 - // 2AM 3AM 1AM 2AM - // | +1 hr | | -1 hr | - // | | | | - // [========== DST ========>) - // - // -=-=-=-=-=- Some Weird Time Zone -=-=-=-=-=-=- - // April 2, 2006 October 29, 2006 - // 1AM 2AM 2AM 3AM - // | -1 hr | | +1 hr | - // | | | | - // [======== DST ========>) - // - bool invalidAtStart = rule.DaylightDelta > TimeSpan.Zero; - - startTime = rule.IsStartDateMarkerForBeginningOfYear() ? - new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) : - daylightTime.Start + (invalidAtStart ? rule.DaylightDelta : TimeSpan.Zero); /* FUTURE: - rule.StandardDelta; */ - - endTime = rule.IsEndDateMarkerForEndOfYear() ? - new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) : - daylightTime.End + (invalidAtStart ? -rule.DaylightDelta : TimeSpan.Zero); - } - - bool isDst = CheckIsDst(startTime, time, endTime, false, rule); - - // If this date was previously converted from a UTC date and we were able to detect that the local - // DateTime would be ambiguous, this data is stored in the DateTime to resolve this ambiguity. - if (isDst && time.Kind == DateTimeKind.Local) - { - // For normal time zones, the ambiguous hour is the last hour of daylight saving when you wind the - // clock back. It is theoretically possible to have a positive delta, (which would really be daylight - // reduction time), where you would have to wind the clock back in the begnning. - if (GetIsAmbiguousTime(time, rule, daylightTime)) - { - isDst = time.IsAmbiguousDaylightSavingTime(); - } - } - - return isDst; - } - - /// - /// Gets the offset that should be used to calculate DST start times from a UTC time. - /// - private TimeSpan GetDaylightSavingsStartOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule, int? ruleIndex) - { - if (rule.NoDaylightTransitions) - { - // use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule - AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule, ruleIndex); - return baseUtcOffset + previousRule.BaseUtcOffsetDelta + previousRule.DaylightDelta; - } - else - { - return baseUtcOffset + rule.BaseUtcOffsetDelta; /* FUTURE: + rule.StandardDelta; */ - } - } - - /// - /// Gets the offset that should be used to calculate DST end times from a UTC time. - /// - private TimeSpan GetDaylightSavingsEndOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule) - { - // NOTE: even NoDaylightTransitions rules use this logic since DST ends w.r.t. the current rule - return baseUtcOffset + rule.BaseUtcOffsetDelta + rule.DaylightDelta; /* FUTURE: + rule.StandardDelta; */ - } - - /// - /// Helper function that checks if a given dateTime is in Daylight Saving Time (DST). - /// This function assumes the dateTime is in UTC and AdjustmentRule is in a different time zone. - /// - private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpan utc, AdjustmentRule rule, int? ruleIndex, out bool isAmbiguousLocalDst, TimeZoneInfo zone) - { - isAmbiguousLocalDst = false; - - if (rule == null) - { - return false; - } - - // Get the daylight changes for the year of the specified time. - DaylightTimeStruct daylightTime = zone.GetDaylightTime(year, rule, ruleIndex); - - // The start and end times represent the range of universal times that are in DST for that year. - // Within that there is an ambiguous hour, usually right at the end, but at the beginning in - // the unusual case of a negative daylight savings delta. - // We need to handle the case if the current rule has daylight saving end by the end of year. If so, we need to check if next year starts with daylight saving on - // and get the actual daylight saving end time. Here is example for such case: - // Converting the UTC datetime "12/31/2011 8:00:00 PM" to "(UTC+03:00) Moscow, St. Petersburg, Volgograd (RTZ 2)" zone. - // In 2011 the daylight saving will go through the end of the year. If we use the end of 2011 as the daylight saving end, - // that will fail the conversion because the UTC time +4 hours (3 hours for the zone UTC offset and 1 hour for daylight saving) will move us to the next year "1/1/2012 12:00 AM", - // checking against the end of 2011 will tell we are not in daylight saving which is wrong and the conversion will be off by one hour. - // Note we handle the similar case when rule year start with daylight saving and previous year end with daylight saving. - - bool ignoreYearAdjustment = false; - TimeSpan dstStartOffset = zone.GetDaylightSavingsStartOffsetFromUtc(utc, rule, ruleIndex); - DateTime startTime; - if (rule.IsStartDateMarkerForBeginningOfYear() && daylightTime.Start.Year > DateTime.MinValue.Year) - { - int? previousYearRuleIndex; - AdjustmentRule? previousYearRule = zone.GetAdjustmentRuleForTime( - new DateTime(daylightTime.Start.Year - 1, 12, 31), - out previousYearRuleIndex); - if (previousYearRule != null && previousYearRule.IsEndDateMarkerForEndOfYear()) - { - DaylightTimeStruct previousDaylightTime = zone.GetDaylightTime( - daylightTime.Start.Year - 1, - previousYearRule, - previousYearRuleIndex); - startTime = previousDaylightTime.Start - utc - previousYearRule.BaseUtcOffsetDelta; - ignoreYearAdjustment = true; - } - else - { - startTime = new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) - dstStartOffset; - } - } - else - { - startTime = daylightTime.Start - dstStartOffset; - } - - TimeSpan dstEndOffset = zone.GetDaylightSavingsEndOffsetFromUtc(utc, rule); - DateTime endTime; - if (rule.IsEndDateMarkerForEndOfYear() && daylightTime.End.Year < DateTime.MaxValue.Year) - { - int? nextYearRuleIndex; - AdjustmentRule? nextYearRule = zone.GetAdjustmentRuleForTime( - new DateTime(daylightTime.End.Year + 1, 1, 1), - out nextYearRuleIndex); - if (nextYearRule != null && nextYearRule.IsStartDateMarkerForBeginningOfYear()) - { - if (nextYearRule.IsEndDateMarkerForEndOfYear()) - { - // next year end with daylight saving on too - endTime = new DateTime(daylightTime.End.Year + 1, 12, 31) - utc - nextYearRule.BaseUtcOffsetDelta - nextYearRule.DaylightDelta; - } - else - { - DaylightTimeStruct nextdaylightTime = zone.GetDaylightTime( - daylightTime.End.Year + 1, - nextYearRule, - nextYearRuleIndex); - endTime = nextdaylightTime.End - utc - nextYearRule.BaseUtcOffsetDelta - nextYearRule.DaylightDelta; - } - ignoreYearAdjustment = true; - } - else - { - endTime = new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) - dstEndOffset; - } - } - else - { - endTime = daylightTime.End - dstEndOffset; - } - - DateTime ambiguousStart; - DateTime ambiguousEnd; - if (daylightTime.Delta.Ticks > 0) - { - ambiguousStart = endTime - daylightTime.Delta; - ambiguousEnd = endTime; - } - else - { - ambiguousStart = startTime; - ambiguousEnd = startTime - daylightTime.Delta; - } - - bool isDst = CheckIsDst(startTime, time, endTime, ignoreYearAdjustment, rule); - - // See if the resulting local time becomes ambiguous. This must be captured here or the - // DateTime will not be able to round-trip back to UTC accurately. - if (isDst) - { - isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd); - - if (!isAmbiguousLocalDst && ambiguousStart.Year != ambiguousEnd.Year) - { - // there exists an extreme corner case where the start or end period is on a year boundary and - // because of this the comparison above might have been performed for a year-early or a year-later - // than it should have been. - DateTime ambiguousStartModified; - DateTime ambiguousEndModified; - try - { - ambiguousStartModified = ambiguousStart.AddYears(1); - ambiguousEndModified = ambiguousEnd.AddYears(1); - isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd); - } - catch (ArgumentOutOfRangeException) { } - - if (!isAmbiguousLocalDst) - { - try - { - ambiguousStartModified = ambiguousStart.AddYears(-1); - ambiguousEndModified = ambiguousEnd.AddYears(-1); - isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd); - } - catch (ArgumentOutOfRangeException) { } - } - } - } - - return isDst; - } - - private static bool CheckIsDst(DateTime startTime, DateTime time, DateTime endTime, bool ignoreYearAdjustment, AdjustmentRule rule) - { - // NoDaylightTransitions AdjustmentRules should never get their year adjusted since they adjust the offset for the - // entire time period - which may be for multiple years - if (!ignoreYearAdjustment && !rule.NoDaylightTransitions) - { - int startTimeYear = startTime.Year; - int endTimeYear = endTime.Year; - - if (startTimeYear != endTimeYear) - { - endTime = endTime.AddYears(startTimeYear - endTimeYear); - } - - int timeYear = time.Year; - - if (startTimeYear != timeYear) - { - time = time.AddYears(startTimeYear - timeYear); - } - } - - if (startTime > endTime) - { - // In southern hemisphere, the daylight saving time starts later in the year, and ends in the beginning of next year. - // Note, the summer in the southern hemisphere begins late in the year. - return (time < endTime || time >= startTime); - } - else if (rule.NoDaylightTransitions) - { - // In NoDaylightTransitions AdjustmentRules, the startTime is always before the endTime, - // and both the start and end times are inclusive - return time >= startTime && time <= endTime; - } - else - { - // In northern hemisphere, the daylight saving time starts in the middle of the year. - return time >= startTime && time < endTime; - } - } - - /// - /// Returns true when the dateTime falls into an ambiguous time range. - /// - /// For example, in Pacific Standard Time on Sunday, October 29, 2006 time jumps from - /// 2AM to 1AM. This means the timeline on Sunday proceeds as follows: - /// 12AM ... [1AM ... 1:59:59AM -> 1AM ... 1:59:59AM] 2AM ... 3AM ... - /// - /// In this example, any DateTime values that fall into the [1AM - 1:59:59AM] range - /// are ambiguous; as it is unclear if these times are in Daylight Saving Time. - /// - private static bool GetIsAmbiguousTime(DateTime time, AdjustmentRule rule, DaylightTimeStruct daylightTime) - { - bool isAmbiguous = false; - if (rule == null || rule.DaylightDelta == TimeSpan.Zero) - { - return isAmbiguous; - } - - DateTime startAmbiguousTime; - DateTime endAmbiguousTime; - - // if at DST start we transition forward in time then there is an ambiguous time range at the DST end - if (rule.DaylightDelta > TimeSpan.Zero) - { - if (rule.IsEndDateMarkerForEndOfYear()) - { // year end with daylight on so there is no ambiguous time - return false; - } - startAmbiguousTime = daylightTime.End; - endAmbiguousTime = daylightTime.End - rule.DaylightDelta; /* FUTURE: + rule.StandardDelta; */ - } - else - { - if (rule.IsStartDateMarkerForBeginningOfYear()) - { // year start with daylight on so there is no ambiguous time - return false; - } - startAmbiguousTime = daylightTime.Start; - endAmbiguousTime = daylightTime.Start + rule.DaylightDelta; /* FUTURE: - rule.StandardDelta; */ - } - - isAmbiguous = (time >= endAmbiguousTime && time < startAmbiguousTime); - - if (!isAmbiguous && startAmbiguousTime.Year != endAmbiguousTime.Year) - { - // there exists an extreme corner case where the start or end period is on a year boundary and - // because of this the comparison above might have been performed for a year-early or a year-later - // than it should have been. - DateTime startModifiedAmbiguousTime; - DateTime endModifiedAmbiguousTime; - try - { - startModifiedAmbiguousTime = startAmbiguousTime.AddYears(1); - endModifiedAmbiguousTime = endAmbiguousTime.AddYears(1); - isAmbiguous = (time >= endModifiedAmbiguousTime && time < startModifiedAmbiguousTime); - } - catch (ArgumentOutOfRangeException) { } - - if (!isAmbiguous) - { - try - { - startModifiedAmbiguousTime = startAmbiguousTime.AddYears(-1); - endModifiedAmbiguousTime = endAmbiguousTime.AddYears(-1); - isAmbiguous = (time >= endModifiedAmbiguousTime && time < startModifiedAmbiguousTime); - } - catch (ArgumentOutOfRangeException) { } - } - } - return isAmbiguous; - } - - /// - /// Helper function that checks if a given DateTime is in an invalid time ("time hole") - /// A "time hole" occurs at a DST transition point when time jumps forward; - /// For example, in Pacific Standard Time on Sunday, April 2, 2006 time jumps from - /// 1:59:59.9999999 to 3AM. The time range 2AM to 2:59:59.9999999AM is the "time hole". - /// A "time hole" is not limited to only occurring at the start of DST, and may occur at - /// the end of DST as well. - /// - private static bool GetIsInvalidTime(DateTime time, AdjustmentRule rule, DaylightTimeStruct daylightTime) - { - bool isInvalid = false; - if (rule == null || rule.DaylightDelta == TimeSpan.Zero) - { - return isInvalid; - } - - DateTime startInvalidTime; - DateTime endInvalidTime; - - // if at DST start we transition forward in time then there is an ambiguous time range at the DST end - if (rule.DaylightDelta < TimeSpan.Zero) - { - // if the year ends with daylight saving on then there cannot be any time-hole's in that year. - if (rule.IsEndDateMarkerForEndOfYear()) - return false; - - startInvalidTime = daylightTime.End; - endInvalidTime = daylightTime.End - rule.DaylightDelta; /* FUTURE: + rule.StandardDelta; */ - } - else - { - // if the year starts with daylight saving on then there cannot be any time-hole's in that year. - if (rule.IsStartDateMarkerForBeginningOfYear()) - return false; - - startInvalidTime = daylightTime.Start; - endInvalidTime = daylightTime.Start + rule.DaylightDelta; /* FUTURE: - rule.StandardDelta; */ - } - - isInvalid = (time >= startInvalidTime && time < endInvalidTime); - - if (!isInvalid && startInvalidTime.Year != endInvalidTime.Year) - { - // there exists an extreme corner case where the start or end period is on a year boundary and - // because of this the comparison above might have been performed for a year-early or a year-later - // than it should have been. - DateTime startModifiedInvalidTime; - DateTime endModifiedInvalidTime; - try - { - startModifiedInvalidTime = startInvalidTime.AddYears(1); - endModifiedInvalidTime = endInvalidTime.AddYears(1); - isInvalid = (time >= startModifiedInvalidTime && time < endModifiedInvalidTime); - } - catch (ArgumentOutOfRangeException) { } - - if (!isInvalid) - { - try - { - startModifiedInvalidTime = startInvalidTime.AddYears(-1); - endModifiedInvalidTime = endInvalidTime.AddYears(-1); - isInvalid = (time >= startModifiedInvalidTime && time < endModifiedInvalidTime); - } - catch (ArgumentOutOfRangeException) { } - } - } - return isInvalid; - } - - /// - /// Helper function that calculates the UTC offset for a dateTime in a timeZone. - /// This function assumes that the dateTime is already converted into the timeZone. - /// - private static TimeSpan GetUtcOffset(DateTime time, TimeZoneInfo zone, TimeZoneInfoOptions flags) - { - TimeSpan baseOffset = zone.BaseUtcOffset; - int? ruleIndex; - AdjustmentRule? rule = zone.GetAdjustmentRuleForTime(time, out ruleIndex); - - if (rule != null) - { - baseOffset = baseOffset + rule.BaseUtcOffsetDelta; - if (rule.HasDaylightSaving) - { - DaylightTimeStruct daylightTime = zone.GetDaylightTime(time.Year, rule, ruleIndex); - bool isDaylightSavings = GetIsDaylightSavings(time, rule, daylightTime, flags); - baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */); - } - } - - return baseOffset; - } - - /// - /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone. - /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone. - /// - private static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone) - { - bool isDaylightSavings; - return GetUtcOffsetFromUtc(time, zone, out isDaylightSavings); - } - - /// - /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone. - /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone. - /// - private static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, out bool isDaylightSavings) - { - bool isAmbiguousLocalDst; - return GetUtcOffsetFromUtc(time, zone, out isDaylightSavings, out isAmbiguousLocalDst); - } - - /// - /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone. - /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone. - /// - internal static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, out bool isDaylightSavings, out bool isAmbiguousLocalDst) - { - isDaylightSavings = false; - isAmbiguousLocalDst = false; - TimeSpan baseOffset = zone.BaseUtcOffset; - int year; - int? ruleIndex; - AdjustmentRule? rule; - - if (time > s_maxDateOnly) - { - rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue, out ruleIndex); - year = 9999; - } - else if (time < s_minDateOnly) - { - rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue, out ruleIndex); - year = 1; - } - else - { - rule = zone.GetAdjustmentRuleForTime(time, dateTimeisUtc: true, ruleIndex: out ruleIndex); - Debug.Assert(rule == null || ruleIndex.HasValue, - "If GetAdjustmentRuleForTime returned an AdjustmentRule, ruleIndex should also be set."); - - // As we get the associated rule using the adjusted targetTime, we should use the adjusted year (targetTime.Year) too as after adding the baseOffset, - // sometimes the year value can change if the input datetime was very close to the beginning or the end of the year. Examples of such cases: - // Libya Standard Time when used with the date 2011-12-31T23:59:59.9999999Z - // "W. Australia Standard Time" used with date 2005-12-31T23:59:00.0000000Z - DateTime targetTime = time + baseOffset; - year = targetTime.Year; - } - - if (rule != null) - { - baseOffset = baseOffset + rule.BaseUtcOffsetDelta; - if (rule.HasDaylightSaving) - { - isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone._baseUtcOffset, rule, ruleIndex, out isAmbiguousLocalDst, zone); - baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */); - } - } - - return baseOffset; - } - - /// - /// Helper function that converts a year and TransitionTime into a DateTime. - /// - internal static DateTime TransitionTimeToDateTime(int year, TransitionTime transitionTime) - { - DateTime value; - DateTime timeOfDay = transitionTime.TimeOfDay; - - if (transitionTime.IsFixedDateRule) - { - // create a DateTime from the passed in year and the properties on the transitionTime - - // if the day is out of range for the month then use the last day of the month - int day = DateTime.DaysInMonth(year, transitionTime.Month); - - value = new DateTime(year, transitionTime.Month, (day < transitionTime.Day) ? day : transitionTime.Day, - timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); - } - else - { - if (transitionTime.Week <= 4) - { - // - // Get the (transitionTime.Week)th Sunday. - // - value = new DateTime(year, transitionTime.Month, 1, - timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); - - int dayOfWeek = (int)value.DayOfWeek; - int delta = (int)transitionTime.DayOfWeek - dayOfWeek; - if (delta < 0) - { - delta += 7; - } - delta += 7 * (transitionTime.Week - 1); - - if (delta > 0) - { - value = value.AddDays(delta); - } - } - else - { - // - // If TransitionWeek is greater than 4, we will get the last week. - // - int daysInMonth = DateTime.DaysInMonth(year, transitionTime.Month); - value = new DateTime(year, transitionTime.Month, daysInMonth, - timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); - - // This is the day of week for the last day of the month. - int dayOfWeek = (int)value.DayOfWeek; - int delta = dayOfWeek - (int)transitionTime.DayOfWeek; - if (delta < 0) - { - delta += 7; - } - - if (delta > 0) - { - value = value.AddDays(-delta); - } - } - } - return value; - } - - /// - /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. - /// - /// This function may return null. - /// - /// assumes cachedData lock is taken - /// - private static TimeZoneInfoResult TryGetTimeZone(string id, bool dstDisabled, out TimeZoneInfo? value, out Exception? e, CachedData cachedData, bool alwaysFallbackToLocalMachine = false) - { - Debug.Assert(Monitor.IsEntered(cachedData)); - - TimeZoneInfoResult result = TimeZoneInfoResult.Success; - e = null; - TimeZoneInfo? match = null; - - // check the cache - if (cachedData._systemTimeZones != null) - { - if (cachedData._systemTimeZones.TryGetValue(id, out match)) - { - if (dstDisabled && match._supportsDaylightSavingTime) - { - // we found a cache hit but we want a time zone without DST and this one has DST data - value = CreateCustomTimeZone(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName); - } - else - { - value = new TimeZoneInfo(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName, - match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false); - } - return result; - } - } - - // Fall back to reading from the local machine when the cache is not fully populated. - // On UNIX, there may be some tzfiles that aren't in the zones.tab file, and thus aren't returned from GetSystemTimeZones(). - // If a caller asks for one of these zones before calling GetSystemTimeZones(), the time zone is returned successfully. But if - // GetSystemTimeZones() is called first, FindSystemTimeZoneById will throw TimeZoneNotFoundException, which is inconsistent. - // To fix this, when 'alwaysFallbackToLocalMachine' is true, even if _allSystemTimeZonesRead is true, try reading the tzfile - // from disk, but don't add the time zone to the list returned from GetSystemTimeZones(). These time zones will only be - // available if asked for directly. - if (!cachedData._allSystemTimeZonesRead || alwaysFallbackToLocalMachine) - { - result = TryGetTimeZoneFromLocalMachine(id, dstDisabled, out value, out e, cachedData); - } - else - { - result = TimeZoneInfoResult.TimeZoneNotFoundException; - value = null; - } - - return result; - } - - private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, bool dstDisabled, out TimeZoneInfo? value, out Exception? e, CachedData cachedData) - { - TimeZoneInfoResult result; - TimeZoneInfo? match; - - result = TryGetTimeZoneFromLocalMachine(id, out match, out e); - - if (result == TimeZoneInfoResult.Success) - { - if (cachedData._systemTimeZones == null) - cachedData._systemTimeZones = new Dictionary(StringComparer.OrdinalIgnoreCase); - - cachedData._systemTimeZones.Add(id, match!); // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 - - if (dstDisabled && match!._supportsDaylightSavingTime) // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 - { - // we found a cache hit but we want a time zone without DST and this one has DST data - value = CreateCustomTimeZone(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName); - } - else - { - value = new TimeZoneInfo(match!._id, match._baseUtcOffset, match._displayName, match._standardDisplayName, // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 - match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false); - } - } - else - { - value = null; - } - - return result; - } - - /// - /// Helper function that performs all of the validation checks for the - /// factory methods and deserialization callback. - /// - private static void ValidateTimeZoneInfo(string id, TimeSpan baseUtcOffset, AdjustmentRule[]? adjustmentRules, out bool adjustmentRulesSupportDst) - { - if (id == null) - { - throw new ArgumentNullException(nameof(id)); - } - - if (id.Length == 0) - { - throw new ArgumentException(SR.Format(SR.Argument_InvalidId, id), nameof(id)); - } - - if (UtcOffsetOutOfRange(baseUtcOffset)) - { - throw new ArgumentOutOfRangeException(nameof(baseUtcOffset), SR.ArgumentOutOfRange_UtcOffset); - } - - if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0) - { - throw new ArgumentException(SR.Argument_TimeSpanHasSeconds, nameof(baseUtcOffset)); - } - - adjustmentRulesSupportDst = false; - - // - // "adjustmentRules" can either be null or a valid array of AdjustmentRule objects. - // A valid array is one that does not contain any null elements and all elements - // are sorted in chronological order - // - - if (adjustmentRules != null && adjustmentRules.Length != 0) - { - adjustmentRulesSupportDst = true; - AdjustmentRule? prev = null; - AdjustmentRule? current = null; - for (int i = 0; i < adjustmentRules.Length; i++) - { - prev = current; - current = adjustmentRules[i]; - - if (current == null) - { - throw new InvalidTimeZoneException(SR.Argument_AdjustmentRulesNoNulls); - } - - if (!IsValidAdjustmentRuleOffest(baseUtcOffset, current)) - { - throw new InvalidTimeZoneException(SR.ArgumentOutOfRange_UtcOffsetAndDaylightDelta); - } - - if (prev != null && current.DateStart <= prev.DateEnd) - { - // verify the rules are in chronological order and the DateStart/DateEnd do not overlap - throw new InvalidTimeZoneException(SR.Argument_AdjustmentRulesOutOfOrder); - } - } - } - } - - private static readonly TimeSpan MaxOffset = TimeSpan.FromHours(14.0); - private static readonly TimeSpan MinOffset = -MaxOffset; - - /// - /// Helper function that validates the TimeSpan is within +/- 14.0 hours - /// - internal static bool UtcOffsetOutOfRange(TimeSpan offset) => - offset < MinOffset || offset > MaxOffset; - - private static TimeSpan GetUtcOffset(TimeSpan baseUtcOffset, AdjustmentRule adjustmentRule) - { - return baseUtcOffset - + adjustmentRule.BaseUtcOffsetDelta - + (adjustmentRule.HasDaylightSaving ? adjustmentRule.DaylightDelta : TimeSpan.Zero); - } - - /// - /// Helper function that performs adjustment rule validation - /// - private static bool IsValidAdjustmentRuleOffest(TimeSpan baseUtcOffset, AdjustmentRule adjustmentRule) - { - TimeSpan utcOffset = GetUtcOffset(baseUtcOffset, adjustmentRule); - return !UtcOffsetOutOfRange(utcOffset); - } - - /// - /// Normalize adjustment rule offset so that it is within valid range - /// This method should not be called at all but is here in case something changes in the future - /// or if really old time zones are present on the OS (no combination is known at the moment) - /// - private static void NormalizeAdjustmentRuleOffset(TimeSpan baseUtcOffset, ref AdjustmentRule adjustmentRule) - { - // Certain time zones such as: - // Time Zone start date end date offset - // ----------------------------------------------------- - // America/Yakutat 0001-01-01 1867-10-18 14:41:00 - // America/Yakutat 1867-10-18 1900-08-20 14:41:00 - // America/Sitka 0001-01-01 1867-10-18 14:58:00 - // America/Sitka 1867-10-18 1900-08-20 14:58:00 - // Asia/Manila 0001-01-01 1844-12-31 -15:56:00 - // Pacific/Guam 0001-01-01 1845-01-01 -14:21:00 - // Pacific/Saipan 0001-01-01 1845-01-01 -14:21:00 - // - // have larger offset than currently supported by framework. - // If for whatever reason we find that time zone exceeding max - // offset of 14h this function will truncate it to the max valid offset. - // Updating max offset may cause problems with interacting with SQL server - // which uses SQL DATETIMEOFFSET field type which was originally designed to be - // bit-for-bit compatible with DateTimeOffset. - - TimeSpan utcOffset = GetUtcOffset(baseUtcOffset, adjustmentRule); - - // utc base offset delta increment - TimeSpan adjustment = TimeSpan.Zero; - - if (utcOffset > MaxOffset) - { - adjustment = MaxOffset - utcOffset; - } - else if (utcOffset < MinOffset) - { - adjustment = MinOffset - utcOffset; - } - - if (adjustment != TimeSpan.Zero) - { - adjustmentRule = AdjustmentRule.CreateAdjustmentRule( - adjustmentRule.DateStart, - adjustmentRule.DateEnd, - adjustmentRule.DaylightDelta, - adjustmentRule.DaylightTransitionStart, - adjustmentRule.DaylightTransitionEnd, - adjustmentRule.BaseUtcOffsetDelta + adjustment, - adjustmentRule.NoDaylightTransitions); - } - } - } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.Serialization; +using System.Threading; + +namespace System +{ + // + // DateTime uses TimeZoneInfo under the hood for IsDaylightSavingTime, IsAmbiguousTime, and GetUtcOffset. + // These TimeZoneInfo APIs can throw ArgumentException when an Invalid-Time is passed in. To avoid this + // unwanted behavior in DateTime public APIs, DateTime internally passes the + // TimeZoneInfoOptions.NoThrowOnInvalidTime flag to internal TimeZoneInfo APIs. + // + // In the future we can consider exposing similar options on the public TimeZoneInfo APIs if there is enough + // demand for this alternate behavior. + // + [Flags] + internal enum TimeZoneInfoOptions + { + None = 1, + NoThrowOnInvalidTime = 2 + }; + + [Serializable] + [System.Runtime.CompilerServices.TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + public sealed partial class TimeZoneInfo : IEquatable, ISerializable, IDeserializationCallback + { + private enum TimeZoneInfoResult + { + Success = 0, + TimeZoneNotFoundException = 1, + InvalidTimeZoneException = 2, + SecurityException = 3 + }; + + private readonly string _id; + private readonly string? _displayName; + private readonly string? _standardDisplayName; + private readonly string? _daylightDisplayName; + private readonly TimeSpan _baseUtcOffset; + private readonly bool _supportsDaylightSavingTime; + private readonly AdjustmentRule[]? _adjustmentRules; + + // constants for TimeZoneInfo.Local and TimeZoneInfo.Utc + private const string UtcId = "UTC"; + private const string LocalId = "Local"; + + private static readonly TimeZoneInfo s_utcTimeZone = CreateCustomTimeZone(UtcId, TimeSpan.Zero, UtcId, UtcId); + + private static CachedData s_cachedData = new CachedData(); + + // + // All cached data are encapsulated in a helper class to allow consistent view even when the data are refreshed using ClearCachedData() + // + // For example, TimeZoneInfo.Local can be cleared by another thread calling TimeZoneInfo.ClearCachedData. Without the consistent snapshot, + // there is a chance that the internal ConvertTime calls will throw since 'source' won't be reference equal to the new TimeZoneInfo.Local. + // + private sealed partial class CachedData + { + private volatile TimeZoneInfo? _localTimeZone; + + private TimeZoneInfo CreateLocal() + { + lock (this) + { + TimeZoneInfo? timeZone = _localTimeZone; + if (timeZone == null) + { + timeZone = GetLocalTimeZone(this); + + // this step is to break the reference equality + // between TimeZoneInfo.Local and a second time zone + // such as "Pacific Standard Time" + timeZone = new TimeZoneInfo( + timeZone._id, + timeZone._baseUtcOffset, + timeZone._displayName, + timeZone._standardDisplayName, + timeZone._daylightDisplayName, + timeZone._adjustmentRules, + disableDaylightSavingTime: false); + + _localTimeZone = timeZone; + } + return timeZone; + } + } + + public TimeZoneInfo Local + { + get + { + TimeZoneInfo? timeZone = _localTimeZone; + if (timeZone == null) + { + timeZone = CreateLocal(); + } + return timeZone; + } + } + + /// + /// Helper function that returns the corresponding DateTimeKind for this TimeZoneInfo. + /// + public DateTimeKind GetCorrespondingKind(TimeZoneInfo? timeZone) + { + // We check reference equality to see if 'this' is the same as + // TimeZoneInfo.Local or TimeZoneInfo.Utc. This check is needed to + // support setting the DateTime Kind property to 'Local' or + // 'Utc' on the ConverTime(...) return value. + // + // Using reference equality instead of value equality was a + // performance based design compromise. The reference equality + // has much greater performance, but it reduces the number of + // returned DateTime's that can be properly set as 'Local' or 'Utc'. + // + // For example, the user could be converting to the TimeZoneInfo returned + // by FindSystemTimeZoneById("Pacific Standard Time") and their local + // machine may be in Pacific time. If we used value equality to determine + // the corresponding Kind then this conversion would be tagged as 'Local'; + // where as we are currently tagging the returned DateTime as 'Unspecified' + // in this example. Only when the user passes in TimeZoneInfo.Local or + // TimeZoneInfo.Utc to the ConvertTime(...) methods will this check succeed. + // + return + ReferenceEquals(timeZone, s_utcTimeZone) ? DateTimeKind.Utc : + ReferenceEquals(timeZone, _localTimeZone) ? DateTimeKind.Local : + DateTimeKind.Unspecified; + } + + public Dictionary? _systemTimeZones; + public ReadOnlyCollection? _readOnlySystemTimeZones; + public bool _allSystemTimeZonesRead; + }; + + // used by GetUtcOffsetFromUtc (DateTime.Now, DateTime.ToLocalTime) for max/min whole-day range checks + private static readonly DateTime s_maxDateOnly = new DateTime(9999, 12, 31); + private static readonly DateTime s_minDateOnly = new DateTime(1, 1, 2); + + public string Id => _id; + + public string DisplayName => _displayName ?? string.Empty; + + public string StandardName => _standardDisplayName ?? string.Empty; + + public string DaylightName => _daylightDisplayName ?? string.Empty; + + public TimeSpan BaseUtcOffset => _baseUtcOffset; + + public bool SupportsDaylightSavingTime => _supportsDaylightSavingTime; + + /// + /// Returns an array of TimeSpan objects representing all of + /// possible UTC offset values for this ambiguous time. + /// + public TimeSpan[] GetAmbiguousTimeOffsets(DateTimeOffset dateTimeOffset) + { + if (!SupportsDaylightSavingTime) + { + throw new ArgumentException(SR.Argument_DateTimeOffsetIsNotAmbiguous, nameof(dateTimeOffset)); + } + + DateTime adjustedTime = ConvertTime(dateTimeOffset, this).DateTime; + + bool isAmbiguous = false; + int? ruleIndex; + AdjustmentRule? rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime, out ruleIndex); + if (rule != null && rule.HasDaylightSaving) + { + DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); + isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime); + } + + if (!isAmbiguous) + { + throw new ArgumentException(SR.Argument_DateTimeOffsetIsNotAmbiguous, nameof(dateTimeOffset)); + } + + // the passed in dateTime is ambiguous in this TimeZoneInfo instance + TimeSpan[] timeSpans = new TimeSpan[2]; + + TimeSpan actualUtcOffset = _baseUtcOffset + rule!.BaseUtcOffsetDelta; + + // the TimeSpan array must be sorted from least to greatest + if (rule.DaylightDelta > TimeSpan.Zero) + { + timeSpans[0] = actualUtcOffset; // FUTURE: + rule.StandardDelta; + timeSpans[1] = actualUtcOffset + rule.DaylightDelta; + } + else + { + timeSpans[0] = actualUtcOffset + rule.DaylightDelta; + timeSpans[1] = actualUtcOffset; // FUTURE: + rule.StandardDelta; + } + return timeSpans; + } + + /// + /// Returns an array of TimeSpan objects representing all of + /// possible UTC offset values for this ambiguous time. + /// + public TimeSpan[] GetAmbiguousTimeOffsets(DateTime dateTime) + { + if (!SupportsDaylightSavingTime) + { + throw new ArgumentException(SR.Argument_DateTimeIsNotAmbiguous, nameof(dateTime)); + } + + DateTime adjustedTime; + if (dateTime.Kind == DateTimeKind.Local) + { + CachedData cachedData = s_cachedData; + adjustedTime = ConvertTime(dateTime, cachedData.Local, this, TimeZoneInfoOptions.None, cachedData); + } + else if (dateTime.Kind == DateTimeKind.Utc) + { + CachedData cachedData = s_cachedData; + adjustedTime = ConvertTime(dateTime, s_utcTimeZone, this, TimeZoneInfoOptions.None, cachedData); + } + else + { + adjustedTime = dateTime; + } + + bool isAmbiguous = false; + int? ruleIndex; + AdjustmentRule? rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime, out ruleIndex); + if (rule != null && rule.HasDaylightSaving) + { + DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); + isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime); + } + + if (!isAmbiguous) + { + throw new ArgumentException(SR.Argument_DateTimeIsNotAmbiguous, nameof(dateTime)); + } + + // the passed in dateTime is ambiguous in this TimeZoneInfo instance + TimeSpan[] timeSpans = new TimeSpan[2]; + TimeSpan actualUtcOffset = _baseUtcOffset + rule!.BaseUtcOffsetDelta; + + // the TimeSpan array must be sorted from least to greatest + if (rule.DaylightDelta > TimeSpan.Zero) + { + timeSpans[0] = actualUtcOffset; // FUTURE: + rule.StandardDelta; + timeSpans[1] = actualUtcOffset + rule.DaylightDelta; + } + else + { + timeSpans[0] = actualUtcOffset + rule.DaylightDelta; + timeSpans[1] = actualUtcOffset; // FUTURE: + rule.StandardDelta; + } + return timeSpans; + } + + // note the time is already adjusted + private AdjustmentRule? GetAdjustmentRuleForAmbiguousOffsets(DateTime adjustedTime, out int? ruleIndex) + { + AdjustmentRule? rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex); + if (rule != null && rule.NoDaylightTransitions && !rule.HasDaylightSaving) + { + // When using NoDaylightTransitions rules, each rule is only for one offset. + // When looking for the Daylight savings rules, and we found the non-DST rule, + // then we get the rule right before this rule. + return GetPreviousAdjustmentRule(rule, ruleIndex); + } + + return rule; + } + + /// + /// Gets the AdjustmentRule that is immediately preceding the specified rule. + /// If the specified rule is the first AdjustmentRule, or it isn't in _adjustmentRules, + /// then the specified rule is returned. + /// + private AdjustmentRule GetPreviousAdjustmentRule(AdjustmentRule rule, int? ruleIndex) + { + Debug.Assert(rule.NoDaylightTransitions, "GetPreviousAdjustmentRule should only be used with NoDaylightTransitions rules."); + Debug.Assert(_adjustmentRules != null); + + if (ruleIndex.HasValue && 0 < ruleIndex.GetValueOrDefault() && ruleIndex.GetValueOrDefault() < _adjustmentRules.Length) + { + return _adjustmentRules[ruleIndex.GetValueOrDefault() - 1]; + } + + AdjustmentRule result = rule; + for (int i = 1; i < _adjustmentRules.Length; i++) + { + // use ReferenceEquals here instead of AdjustmentRule.Equals because + // ReferenceEquals is much faster. This is safe because all the callers + // of GetPreviousAdjustmentRule pass in a rule that was retrieved from + // _adjustmentRules. A different approach will be needed if this ever changes. + if (ReferenceEquals(rule, _adjustmentRules[i])) + { + result = _adjustmentRules[i - 1]; + break; + } + } + return result; + } + + /// + /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance. + /// + public TimeSpan GetUtcOffset(DateTimeOffset dateTimeOffset) => + GetUtcOffsetFromUtc(dateTimeOffset.UtcDateTime, this); + + /// + /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance. + /// + public TimeSpan GetUtcOffset(DateTime dateTime) => + GetUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime, s_cachedData); + + // Shortcut for TimeZoneInfo.Local.GetUtcOffset + internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) + { + CachedData cachedData = s_cachedData; + return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData); + } + + /// + /// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance. + /// + internal TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags) => + GetUtcOffset(dateTime, flags, s_cachedData); + + private TimeSpan GetUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags, CachedData cachedData) + { + if (dateTime.Kind == DateTimeKind.Local) + { + if (cachedData.GetCorrespondingKind(this) != DateTimeKind.Local) + { + // + // normal case of converting from Local to Utc and then getting the offset from the UTC DateTime + // + DateTime adjustedTime = ConvertTime(dateTime, cachedData.Local, s_utcTimeZone, flags); + return GetUtcOffsetFromUtc(adjustedTime, this); + } + + // + // Fall through for TimeZoneInfo.Local.GetUtcOffset(date) + // to handle an edge case with Invalid-Times for DateTime formatting: + // + // Consider the invalid PST time "2007-03-11T02:00:00.0000000-08:00" + // + // By directly calling GetUtcOffset instead of converting to UTC and then calling GetUtcOffsetFromUtc + // the correct invalid offset of "-08:00" is returned. In the normal case of converting to UTC as an + // interim-step, the invalid time is adjusted into a *valid* UTC time which causes a change in output: + // + // 1) invalid PST time "2007-03-11T02:00:00.0000000-08:00" + // 2) converted to UTC "2007-03-11T10:00:00.0000000Z" + // 3) offset returned "2007-03-11T03:00:00.0000000-07:00" + // + } + else if (dateTime.Kind == DateTimeKind.Utc) + { + if (cachedData.GetCorrespondingKind(this) == DateTimeKind.Utc) + { + return _baseUtcOffset; + } + else + { + // + // passing in a UTC dateTime to a non-UTC TimeZoneInfo instance is a + // special Loss-Less case. + // + return GetUtcOffsetFromUtc(dateTime, this); + } + } + + return GetUtcOffset(dateTime, this, flags); + } + + /// + /// Returns true if the time is during the ambiguous time period + /// for the current TimeZoneInfo instance. + /// + public bool IsAmbiguousTime(DateTimeOffset dateTimeOffset) + { + if (!_supportsDaylightSavingTime) + { + return false; + } + + DateTimeOffset adjustedTime = ConvertTime(dateTimeOffset, this); + return IsAmbiguousTime(adjustedTime.DateTime); + } + + /// + /// Returns true if the time is during the ambiguous time period + /// for the current TimeZoneInfo instance. + /// + public bool IsAmbiguousTime(DateTime dateTime) => + IsAmbiguousTime(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime); + + /// + /// Returns true if the time is during the ambiguous time period + /// for the current TimeZoneInfo instance. + /// + internal bool IsAmbiguousTime(DateTime dateTime, TimeZoneInfoOptions flags) + { + if (!_supportsDaylightSavingTime) + { + return false; + } + + CachedData cachedData = s_cachedData; + DateTime adjustedTime = + dateTime.Kind == DateTimeKind.Local ? ConvertTime(dateTime, cachedData.Local, this, flags, cachedData) : + dateTime.Kind == DateTimeKind.Utc ? ConvertTime(dateTime, s_utcTimeZone, this, flags, cachedData) : + dateTime; + + int? ruleIndex; + AdjustmentRule? rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex); + if (rule != null && rule.HasDaylightSaving) + { + DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); + return GetIsAmbiguousTime(adjustedTime, rule, daylightTime); + } + return false; + } + + /// + /// Returns true if the time is during Daylight Saving time for the current TimeZoneInfo instance. + /// + public bool IsDaylightSavingTime(DateTimeOffset dateTimeOffset) + { + bool isDaylightSavingTime; + GetUtcOffsetFromUtc(dateTimeOffset.UtcDateTime, this, out isDaylightSavingTime); + return isDaylightSavingTime; + } + + /// + /// Returns true if the time is during Daylight Saving time for the current TimeZoneInfo instance. + /// + public bool IsDaylightSavingTime(DateTime dateTime) => + IsDaylightSavingTime(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime, s_cachedData); + + /// + /// Returns true if the time is during Daylight Saving time for the current TimeZoneInfo instance. + /// + internal bool IsDaylightSavingTime(DateTime dateTime, TimeZoneInfoOptions flags) => + IsDaylightSavingTime(dateTime, flags, s_cachedData); + + private bool IsDaylightSavingTime(DateTime dateTime, TimeZoneInfoOptions flags, CachedData cachedData) + { + // + // dateTime.Kind is UTC, then time will be converted from UTC + // into current instance's timezone + // dateTime.Kind is Local, then time will be converted from Local + // into current instance's timezone + // dateTime.Kind is UnSpecified, then time is already in + // current instance's timezone + // + // Our DateTime handles ambiguous times, (one is in the daylight and + // one is in standard.) If a new DateTime is constructed during ambiguous + // time, it is defaulted to "Standard" (i.e. this will return false). + // For Invalid times, we will return false + + if (!_supportsDaylightSavingTime || _adjustmentRules == null) + { + return false; + } + + DateTime adjustedTime; + // + // handle any Local/Utc special cases... + // + if (dateTime.Kind == DateTimeKind.Local) + { + adjustedTime = ConvertTime(dateTime, cachedData.Local, this, flags, cachedData); + } + else if (dateTime.Kind == DateTimeKind.Utc) + { + if (cachedData.GetCorrespondingKind(this) == DateTimeKind.Utc) + { + // simple always false case: TimeZoneInfo.Utc.IsDaylightSavingTime(dateTime, flags); + return false; + } + else + { + // + // passing in a UTC dateTime to a non-UTC TimeZoneInfo instance is a + // special Loss-Less case. + // + bool isDaylightSavings; + GetUtcOffsetFromUtc(dateTime, this, out isDaylightSavings); + return isDaylightSavings; + } + } + else + { + adjustedTime = dateTime; + } + + // + // handle the normal cases... + // + int? ruleIndex; + AdjustmentRule? rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex); + if (rule != null && rule.HasDaylightSaving) + { + DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); + return GetIsDaylightSavings(adjustedTime, rule, daylightTime, flags); + } + else + { + return false; + } + } + + /// + /// Returns true when dateTime falls into a "hole in time". + /// + public bool IsInvalidTime(DateTime dateTime) + { + bool isInvalid = false; + + if ((dateTime.Kind == DateTimeKind.Unspecified) || + (dateTime.Kind == DateTimeKind.Local && s_cachedData.GetCorrespondingKind(this) == DateTimeKind.Local)) + { + // only check Unspecified and (Local when this TimeZoneInfo instance is Local) + int? ruleIndex; + AdjustmentRule? rule = GetAdjustmentRuleForTime(dateTime, out ruleIndex); + + if (rule != null && rule.HasDaylightSaving) + { + DaylightTimeStruct daylightTime = GetDaylightTime(dateTime.Year, rule, ruleIndex); + isInvalid = GetIsInvalidTime(dateTime, rule, daylightTime); + } + else + { + isInvalid = false; + } + } + + return isInvalid; + } + + /// + /// Clears data from static members. + /// + public static void ClearCachedData() + { + // Clear a fresh instance of cached data + s_cachedData = new CachedData(); + } + + /// + /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone. + /// + public static DateTimeOffset ConvertTimeBySystemTimeZoneId(DateTimeOffset dateTimeOffset, string destinationTimeZoneId) => + ConvertTime(dateTimeOffset, FindSystemTimeZoneById(destinationTimeZoneId)); + + /// + /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone. + /// + public static DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, string destinationTimeZoneId) => + ConvertTime(dateTime, FindSystemTimeZoneById(destinationTimeZoneId)); + + /// + /// Converts the value of a DateTime object from sourceTimeZone to destinationTimeZone. + /// + public static DateTime ConvertTimeBySystemTimeZoneId(DateTime dateTime, string sourceTimeZoneId, string destinationTimeZoneId) + { + if (dateTime.Kind == DateTimeKind.Local && string.Equals(sourceTimeZoneId, Local.Id, StringComparison.OrdinalIgnoreCase)) + { + // TimeZoneInfo.Local can be cleared by another thread calling TimeZoneInfo.ClearCachedData. + // Take snapshot of cached data to guarantee this method will not be impacted by the ClearCachedData call. + // Without the snapshot, there is a chance that ConvertTime will throw since 'source' won't + // be reference equal to the new TimeZoneInfo.Local + // + CachedData cachedData = s_cachedData; + return ConvertTime(dateTime, cachedData.Local, FindSystemTimeZoneById(destinationTimeZoneId), TimeZoneInfoOptions.None, cachedData); + } + else if (dateTime.Kind == DateTimeKind.Utc && string.Equals(sourceTimeZoneId, Utc.Id, StringComparison.OrdinalIgnoreCase)) + { + return ConvertTime(dateTime, s_utcTimeZone, FindSystemTimeZoneById(destinationTimeZoneId), TimeZoneInfoOptions.None, s_cachedData); + } + else + { + return ConvertTime(dateTime, FindSystemTimeZoneById(sourceTimeZoneId), FindSystemTimeZoneById(destinationTimeZoneId)); + } + } + + /// + /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone + /// + public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZoneInfo destinationTimeZone) + { + if (destinationTimeZone == null) + { + throw new ArgumentNullException(nameof(destinationTimeZone)); + } + + // calculate the destination time zone offset + DateTime utcDateTime = dateTimeOffset.UtcDateTime; + TimeSpan destinationOffset = GetUtcOffsetFromUtc(utcDateTime, destinationTimeZone); + + // check for overflow + long ticks = utcDateTime.Ticks + destinationOffset.Ticks; + + return + ticks > DateTimeOffset.MaxValue.Ticks ? DateTimeOffset.MaxValue : + ticks < DateTimeOffset.MinValue.Ticks ? DateTimeOffset.MinValue : + new DateTimeOffset(ticks, destinationOffset); + } + + /// + /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone + /// + public static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo destinationTimeZone) + { + if (destinationTimeZone == null) + { + throw new ArgumentNullException(nameof(destinationTimeZone)); + } + + // Special case to give a way clearing the cache without exposing ClearCachedData() + if (dateTime.Ticks == 0) + { + ClearCachedData(); + } + CachedData cachedData = s_cachedData; + TimeZoneInfo sourceTimeZone = dateTime.Kind == DateTimeKind.Utc ? s_utcTimeZone : cachedData.Local; + return ConvertTime(dateTime, sourceTimeZone, destinationTimeZone, TimeZoneInfoOptions.None, cachedData); + } + + /// + /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone + /// + public static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone) => + ConvertTime(dateTime, sourceTimeZone, destinationTimeZone, TimeZoneInfoOptions.None, s_cachedData); + + /// + /// Converts the value of the dateTime object from sourceTimeZone to destinationTimeZone + /// + internal static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone, TimeZoneInfoOptions flags) => + ConvertTime(dateTime, sourceTimeZone, destinationTimeZone, flags, s_cachedData); + + private static DateTime ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone, TimeZoneInfoOptions flags, CachedData cachedData) + { + if (sourceTimeZone == null) + { + throw new ArgumentNullException(nameof(sourceTimeZone)); + } + + if (destinationTimeZone == null) + { + throw new ArgumentNullException(nameof(destinationTimeZone)); + } + + DateTimeKind sourceKind = cachedData.GetCorrespondingKind(sourceTimeZone); + if (((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) && (dateTime.Kind != DateTimeKind.Unspecified) && (dateTime.Kind != sourceKind)) + { + throw new ArgumentException(SR.Argument_ConvertMismatch, nameof(sourceTimeZone)); + } + + // + // check to see if the DateTime is in an invalid time range. This check + // requires the current AdjustmentRule and DaylightTime - which are also + // needed to calculate 'sourceOffset' in the normal conversion case. + // By calculating the 'sourceOffset' here we improve the + // performance for the normal case at the expense of the 'ArgumentException' + // case and Loss-less Local special cases. + // + int? sourceRuleIndex; + AdjustmentRule? sourceRule = sourceTimeZone.GetAdjustmentRuleForTime(dateTime, out sourceRuleIndex); + TimeSpan sourceOffset = sourceTimeZone.BaseUtcOffset; + + if (sourceRule != null) + { + sourceOffset = sourceOffset + sourceRule.BaseUtcOffsetDelta; + if (sourceRule.HasDaylightSaving) + { + bool sourceIsDaylightSavings = false; + DaylightTimeStruct sourceDaylightTime = sourceTimeZone.GetDaylightTime(dateTime.Year, sourceRule, sourceRuleIndex); + + // 'dateTime' might be in an invalid time range since it is in an AdjustmentRule + // period that supports DST + if (((flags & TimeZoneInfoOptions.NoThrowOnInvalidTime) == 0) && GetIsInvalidTime(dateTime, sourceRule, sourceDaylightTime)) + { + throw new ArgumentException(SR.Argument_DateTimeIsInvalid, nameof(dateTime)); + } + sourceIsDaylightSavings = GetIsDaylightSavings(dateTime, sourceRule, sourceDaylightTime, flags); + + // adjust the sourceOffset according to the Adjustment Rule / Daylight Saving Rule + sourceOffset += (sourceIsDaylightSavings ? sourceRule.DaylightDelta : TimeSpan.Zero /*FUTURE: sourceRule.StandardDelta*/); + } + } + + DateTimeKind targetKind = cachedData.GetCorrespondingKind(destinationTimeZone); + + // handle the special case of Loss-less Local->Local and UTC->UTC) + if (dateTime.Kind != DateTimeKind.Unspecified && sourceKind != DateTimeKind.Unspecified && sourceKind == targetKind) + { + return dateTime; + } + + long utcTicks = dateTime.Ticks - sourceOffset.Ticks; + + // handle the normal case by converting from 'source' to UTC and then to 'target' + bool isAmbiguousLocalDst; + DateTime targetConverted = ConvertUtcToTimeZone(utcTicks, destinationTimeZone, out isAmbiguousLocalDst); + + if (targetKind == DateTimeKind.Local) + { + // Because the ticks conversion between UTC and local is lossy, we need to capture whether the + // time is in a repeated hour so that it can be passed to the DateTime constructor. + return new DateTime(targetConverted.Ticks, DateTimeKind.Local, isAmbiguousLocalDst); + } + else + { + return new DateTime(targetConverted.Ticks, targetKind); + } + } + + /// + /// Converts the value of a DateTime object from Coordinated Universal Time (UTC) to the destinationTimeZone. + /// + public static DateTime ConvertTimeFromUtc(DateTime dateTime, TimeZoneInfo destinationTimeZone) => + ConvertTime(dateTime, s_utcTimeZone, destinationTimeZone, TimeZoneInfoOptions.None, s_cachedData); + + /// + /// Converts the value of a DateTime object to Coordinated Universal Time (UTC). + /// + public static DateTime ConvertTimeToUtc(DateTime dateTime) + { + if (dateTime.Kind == DateTimeKind.Utc) + { + return dateTime; + } + CachedData cachedData = s_cachedData; + return ConvertTime(dateTime, cachedData.Local, s_utcTimeZone, TimeZoneInfoOptions.None, cachedData); + } + + /// + /// Converts the value of a DateTime object to Coordinated Universal Time (UTC). + /// + internal static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags) + { + if (dateTime.Kind == DateTimeKind.Utc) + { + return dateTime; + } + CachedData cachedData = s_cachedData; + return ConvertTime(dateTime, cachedData.Local, s_utcTimeZone, flags, cachedData); + } + + /// + /// Converts the value of a DateTime object to Coordinated Universal Time (UTC). + /// + public static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfo sourceTimeZone) => + ConvertTime(dateTime, sourceTimeZone, s_utcTimeZone, TimeZoneInfoOptions.None, s_cachedData); + + /// + /// Returns value equality. Equals does not compare any localizable + /// String objects (DisplayName, StandardName, DaylightName). + /// + public bool Equals(TimeZoneInfo? other) => + other != null && + string.Equals(_id, other._id, StringComparison.OrdinalIgnoreCase) && + HasSameRules(other); + + public override bool Equals(object? obj) => Equals(obj as TimeZoneInfo); + + public static TimeZoneInfo FromSerializedString(string source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + if (source.Length == 0) + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidSerializedString, source), nameof(source)); + } + + return StringSerializer.GetDeserializedTimeZoneInfo(source); + } + + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(_id); + + /// + /// Returns a containing all valid TimeZone's + /// from the local machine. The entries in the collection are sorted by + /// . + /// This method does *not* throw TimeZoneNotFoundException or InvalidTimeZoneException. + /// + public static ReadOnlyCollection GetSystemTimeZones() + { + CachedData cachedData = s_cachedData; + + lock (cachedData) + { + if (cachedData._readOnlySystemTimeZones == null) + { + PopulateAllSystemTimeZones(cachedData); + cachedData._allSystemTimeZonesRead = true; + + List list; + if (cachedData._systemTimeZones != null) + { + // return a collection of the cached system time zones + list = new List(cachedData._systemTimeZones.Values); + } + else + { + // return an empty collection + list = new List(); + } + + // sort and copy the TimeZoneInfo's into a ReadOnlyCollection for the user + list.Sort((x, y) => + { + // sort by BaseUtcOffset first and by DisplayName second - this is similar to the Windows Date/Time control panel + int comparison = x.BaseUtcOffset.CompareTo(y.BaseUtcOffset); + return comparison == 0 ? string.CompareOrdinal(x.DisplayName, y.DisplayName) : comparison; + }); + + cachedData._readOnlySystemTimeZones = new ReadOnlyCollection(list); + } + } + return cachedData._readOnlySystemTimeZones; + } + + /// + /// Value equality on the "adjustmentRules" array + /// + public bool HasSameRules(TimeZoneInfo other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + // check the utcOffset and supportsDaylightSavingTime members + if (_baseUtcOffset != other._baseUtcOffset || + _supportsDaylightSavingTime != other._supportsDaylightSavingTime) + { + return false; + } + + bool sameRules; + AdjustmentRule[]? currentRules = _adjustmentRules; + AdjustmentRule[]? otherRules = other._adjustmentRules; + + sameRules = + (currentRules == null && otherRules == null) || + (currentRules != null && otherRules != null); + + if (!sameRules) + { + // AdjustmentRule array mismatch + return false; + } + + if (currentRules != null) + { + if (currentRules.Length != otherRules!.Length) + { + // AdjustmentRule array length mismatch + return false; + } + + for (int i = 0; i < currentRules.Length; i++) + { + if (!(currentRules[i]).Equals(otherRules[i])) + { + // AdjustmentRule value-equality mismatch + return false; + } + } + } + return sameRules; + } + + /// + /// Returns a TimeZoneInfo instance that represents the local time on the machine. + /// Accessing this property may throw InvalidTimeZoneException or COMException + /// if the machine is in an unstable or corrupt state. + /// + public static TimeZoneInfo Local => s_cachedData.Local; + + // + // ToSerializedString - + // + // "TimeZoneInfo" := TimeZoneInfo Data;[AdjustmentRule Data 1];...;[AdjustmentRule Data N] + // + // "TimeZoneInfo Data" := <_id>;<_baseUtcOffset>;<_displayName>; + // <_standardDisplayName>;<_daylightDispayName>; + // + // "AdjustmentRule Data" := ;;; + // [TransitionTime Data DST Start] + // [TransitionTime Data DST End] + // + // "TransitionTime Data" += ;;;; + // + public string ToSerializedString() => StringSerializer.GetSerializedString(this); + + /// + /// Returns the : "(GMT-08:00) Pacific Time (US & Canada); Tijuana" + /// + public override string ToString() => DisplayName; + + /// + /// Returns a TimeZoneInfo instance that represents Universal Coordinated Time (UTC) + /// + public static TimeZoneInfo Utc => s_utcTimeZone; + + private TimeZoneInfo( + string id, + TimeSpan baseUtcOffset, + string? displayName, + string? standardDisplayName, + string? daylightDisplayName, + AdjustmentRule[]? adjustmentRules, + bool disableDaylightSavingTime) + { + bool adjustmentRulesSupportDst; + ValidateTimeZoneInfo(id, baseUtcOffset, adjustmentRules, out adjustmentRulesSupportDst); + + _id = id; + _baseUtcOffset = baseUtcOffset; + _displayName = displayName; + _standardDisplayName = standardDisplayName; + _daylightDisplayName = disableDaylightSavingTime ? null : daylightDisplayName; + _supportsDaylightSavingTime = adjustmentRulesSupportDst && !disableDaylightSavingTime; + _adjustmentRules = adjustmentRules; + } + + /// + /// Returns a simple TimeZoneInfo instance that does not support Daylight Saving Time. + /// + public static TimeZoneInfo CreateCustomTimeZone( + string id, + TimeSpan baseUtcOffset, + string? displayName, + string? standardDisplayName) + { + return new TimeZoneInfo( + id, + baseUtcOffset, + displayName, + standardDisplayName, + standardDisplayName, + adjustmentRules: null, + disableDaylightSavingTime: false); + } + + /// + /// Returns a TimeZoneInfo instance that may support Daylight Saving Time. + /// + public static TimeZoneInfo CreateCustomTimeZone( + string id, + TimeSpan baseUtcOffset, + string? displayName, + string? standardDisplayName, + string? daylightDisplayName, + AdjustmentRule[]? adjustmentRules) + { + return CreateCustomTimeZone( + id, + baseUtcOffset, + displayName, + standardDisplayName, + daylightDisplayName, + adjustmentRules, + disableDaylightSavingTime: false); + } + + /// + /// Returns a TimeZoneInfo instance that may support Daylight Saving Time. + /// + public static TimeZoneInfo CreateCustomTimeZone( + string id, + TimeSpan baseUtcOffset, + string? displayName, + string? standardDisplayName, + string? daylightDisplayName, + AdjustmentRule[]? adjustmentRules, + bool disableDaylightSavingTime) + { + if (!disableDaylightSavingTime && adjustmentRules?.Length > 0) + { + adjustmentRules = (AdjustmentRule[])adjustmentRules.Clone(); + } + + return new TimeZoneInfo( + id, + baseUtcOffset, + displayName, + standardDisplayName, + daylightDisplayName, + adjustmentRules, + disableDaylightSavingTime); + } + + void IDeserializationCallback.OnDeserialization(object sender) + { + try + { + bool adjustmentRulesSupportDst; + ValidateTimeZoneInfo(_id, _baseUtcOffset, _adjustmentRules, out adjustmentRulesSupportDst); + + if (adjustmentRulesSupportDst != _supportsDaylightSavingTime) + { + throw new SerializationException(SR.Format(SR.Serialization_CorruptField, "SupportsDaylightSavingTime")); + } + } + catch (ArgumentException e) + { + throw new SerializationException(SR.Serialization_InvalidData, e); + } + catch (InvalidTimeZoneException e) + { + throw new SerializationException(SR.Serialization_InvalidData, e); + } + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + + info.AddValue("Id", _id); // Do not rename (binary serialization) + info.AddValue("DisplayName", _displayName); // Do not rename (binary serialization) + info.AddValue("StandardName", _standardDisplayName); // Do not rename (binary serialization) + info.AddValue("DaylightName", _daylightDisplayName); // Do not rename (binary serialization) + info.AddValue("BaseUtcOffset", _baseUtcOffset); // Do not rename (binary serialization) + info.AddValue("AdjustmentRules", _adjustmentRules); // Do not rename (binary serialization) + info.AddValue("SupportsDaylightSavingTime", _supportsDaylightSavingTime); // Do not rename (binary serialization) + } + + private TimeZoneInfo(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + + _id = (string)info.GetValue("Id", typeof(string))!; // Do not rename (binary serialization) + _displayName = (string?)info.GetValue("DisplayName", typeof(string)); // Do not rename (binary serialization) + _standardDisplayName = (string?)info.GetValue("StandardName", typeof(string)); // Do not rename (binary serialization) + _daylightDisplayName = (string?)info.GetValue("DaylightName", typeof(string)); // Do not rename (binary serialization) + _baseUtcOffset = (TimeSpan)info.GetValue("BaseUtcOffset", typeof(TimeSpan))!; // Do not rename (binary serialization) + _adjustmentRules = (AdjustmentRule[]?)info.GetValue("AdjustmentRules", typeof(AdjustmentRule[])); // Do not rename (binary serialization) + _supportsDaylightSavingTime = (bool)info.GetValue("SupportsDaylightSavingTime", typeof(bool))!; // Do not rename (binary serialization) + } + + private AdjustmentRule? GetAdjustmentRuleForTime(DateTime dateTime, out int? ruleIndex) + { + AdjustmentRule? result = GetAdjustmentRuleForTime(dateTime, dateTimeisUtc: false, ruleIndex: out ruleIndex); + Debug.Assert(result == null || ruleIndex.HasValue, "If an AdjustmentRule was found, ruleIndex should also be set."); + + return result; + } + + private AdjustmentRule? GetAdjustmentRuleForTime(DateTime dateTime, bool dateTimeisUtc, out int? ruleIndex) + { + if (_adjustmentRules == null || _adjustmentRules.Length == 0) + { + ruleIndex = null; + return null; + } + + // Only check the whole-date portion of the dateTime for DateTimeKind.Unspecified rules - + // This is because the AdjustmentRule DateStart & DateEnd are stored as + // Date-only values {4/2/2006 - 10/28/2006} but actually represent the + // time span {4/2/2006@00:00:00.00000 - 10/28/2006@23:59:59.99999} + DateTime date = dateTimeisUtc ? + (dateTime + BaseUtcOffset).Date : + dateTime.Date; + + int low = 0; + int high = _adjustmentRules.Length - 1; + + while (low <= high) + { + int median = low + ((high - low) >> 1); + + AdjustmentRule rule = _adjustmentRules[median]; + AdjustmentRule previousRule = median > 0 ? _adjustmentRules[median - 1] : rule; + + int compareResult = CompareAdjustmentRuleToDateTime(rule, previousRule, dateTime, date, dateTimeisUtc); + if (compareResult == 0) + { + ruleIndex = median; + return rule; + } + else if (compareResult < 0) + { + low = median + 1; + } + else + { + high = median - 1; + } + } + + ruleIndex = null; + return null; + } + + /// + /// Determines if 'rule' is the correct AdjustmentRule for the given dateTime. + /// + /// + /// A value less than zero if rule is for times before dateTime. + /// Zero if rule is correct for dateTime. + /// A value greater than zero if rule is for times after dateTime. + /// + private int CompareAdjustmentRuleToDateTime(AdjustmentRule rule, AdjustmentRule previousRule, + DateTime dateTime, DateTime dateOnly, bool dateTimeisUtc) + { + bool isAfterStart; + if (rule.DateStart.Kind == DateTimeKind.Utc) + { + DateTime dateTimeToCompare = dateTimeisUtc ? + dateTime : + // use the previous rule to compute the dateTimeToCompare, since the time daylight savings "switches" + // is based on the previous rule's offset + ConvertToUtc(dateTime, previousRule.DaylightDelta, previousRule.BaseUtcOffsetDelta); + + isAfterStart = dateTimeToCompare >= rule.DateStart; + } + else + { + // if the rule's DateStart is Unspecified, then use the whole-date portion + isAfterStart = dateOnly >= rule.DateStart; + } + + if (!isAfterStart) + { + return 1; + } + + bool isBeforeEnd; + if (rule.DateEnd.Kind == DateTimeKind.Utc) + { + DateTime dateTimeToCompare = dateTimeisUtc ? + dateTime : + ConvertToUtc(dateTime, rule.DaylightDelta, rule.BaseUtcOffsetDelta); + + isBeforeEnd = dateTimeToCompare <= rule.DateEnd; + } + else + { + // if the rule's DateEnd is Unspecified, then use the whole-date portion + isBeforeEnd = dateOnly <= rule.DateEnd; + } + + return isBeforeEnd ? 0 : -1; + } + + /// + /// Converts the dateTime to UTC using the specified deltas. + /// + private DateTime ConvertToUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta) => + ConvertToFromUtc(dateTime, daylightDelta, baseUtcOffsetDelta, convertToUtc: true); + + /// + /// Converts the dateTime from UTC using the specified deltas. + /// + private DateTime ConvertFromUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta) => + ConvertToFromUtc(dateTime, daylightDelta, baseUtcOffsetDelta, convertToUtc: false); + + /// + /// Converts the dateTime to or from UTC using the specified deltas. + /// + private DateTime ConvertToFromUtc(DateTime dateTime, TimeSpan daylightDelta, TimeSpan baseUtcOffsetDelta, bool convertToUtc) + { + TimeSpan offset = BaseUtcOffset + daylightDelta + baseUtcOffsetDelta; + if (convertToUtc) + { + offset = offset.Negate(); + } + + long ticks = dateTime.Ticks + offset.Ticks; + + return + ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue : + ticks < DateTime.MinValue.Ticks ? DateTime.MinValue : + new DateTime(ticks); + } + + /// + /// Helper function that converts a dateTime from UTC into the destinationTimeZone + /// - Returns DateTime.MaxValue when the converted value is too large. + /// - Returns DateTime.MinValue when the converted value is too small. + /// + private static DateTime ConvertUtcToTimeZone(long ticks, TimeZoneInfo destinationTimeZone, out bool isAmbiguousLocalDst) + { + // used to calculate the UTC offset in the destinationTimeZone + DateTime utcConverted = + ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue : + ticks < DateTime.MinValue.Ticks ? DateTime.MinValue : + new DateTime(ticks); + + // verify the time is between MinValue and MaxValue in the new time zone + TimeSpan offset = GetUtcOffsetFromUtc(utcConverted, destinationTimeZone, out isAmbiguousLocalDst); + ticks += offset.Ticks; + + return + ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue : + ticks < DateTime.MinValue.Ticks ? DateTime.MinValue : + new DateTime(ticks); + } + + /// + /// Helper function that returns a DaylightTime from a year and AdjustmentRule. + /// + private DaylightTimeStruct GetDaylightTime(int year, AdjustmentRule rule, int? ruleIndex) + { + TimeSpan delta = rule.DaylightDelta; + DateTime startTime; + DateTime endTime; + if (rule.NoDaylightTransitions) + { + // NoDaylightTransitions rules don't use DaylightTransition Start and End, instead + // the DateStart and DateEnd are UTC times that represent when daylight savings time changes. + // Convert the UTC times into adjusted time zone times. + + // use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule + AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule, ruleIndex); + startTime = ConvertFromUtc(rule.DateStart, previousRule.DaylightDelta, previousRule.BaseUtcOffsetDelta); + + endTime = ConvertFromUtc(rule.DateEnd, rule.DaylightDelta, rule.BaseUtcOffsetDelta); + } + else + { + startTime = TransitionTimeToDateTime(year, rule.DaylightTransitionStart); + endTime = TransitionTimeToDateTime(year, rule.DaylightTransitionEnd); + } + return new DaylightTimeStruct(startTime, endTime, delta); + } + + /// + /// Helper function that checks if a given dateTime is in Daylight Saving Time (DST). + /// This function assumes the dateTime and AdjustmentRule are both in the same time zone. + /// + private static bool GetIsDaylightSavings(DateTime time, AdjustmentRule rule, DaylightTimeStruct daylightTime, TimeZoneInfoOptions flags) + { + if (rule == null) + { + return false; + } + + DateTime startTime; + DateTime endTime; + + if (time.Kind == DateTimeKind.Local) + { + // startTime and endTime represent the period from either the start of + // DST to the end and ***includes*** the potentially overlapped times + startTime = rule.IsStartDateMarkerForBeginningOfYear() ? + new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) : + daylightTime.Start + daylightTime.Delta; + + endTime = rule.IsEndDateMarkerForEndOfYear() ? + new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) : + daylightTime.End; + } + else + { + // startTime and endTime represent the period from either the start of DST to the end and + // ***does not include*** the potentially overlapped times + // + // -=-=-=-=-=- Pacific Standard Time -=-=-=-=-=-=- + // April 2, 2006 October 29, 2006 + // 2AM 3AM 1AM 2AM + // | +1 hr | | -1 hr | + // | | | | + // [========== DST ========>) + // + // -=-=-=-=-=- Some Weird Time Zone -=-=-=-=-=-=- + // April 2, 2006 October 29, 2006 + // 1AM 2AM 2AM 3AM + // | -1 hr | | +1 hr | + // | | | | + // [======== DST ========>) + // + bool invalidAtStart = rule.DaylightDelta > TimeSpan.Zero; + + startTime = rule.IsStartDateMarkerForBeginningOfYear() ? + new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) : + daylightTime.Start + (invalidAtStart ? rule.DaylightDelta : TimeSpan.Zero); /* FUTURE: - rule.StandardDelta; */ + + endTime = rule.IsEndDateMarkerForEndOfYear() ? + new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) : + daylightTime.End + (invalidAtStart ? -rule.DaylightDelta : TimeSpan.Zero); + } + + bool isDst = CheckIsDst(startTime, time, endTime, false, rule); + + // If this date was previously converted from a UTC date and we were able to detect that the local + // DateTime would be ambiguous, this data is stored in the DateTime to resolve this ambiguity. + if (isDst && time.Kind == DateTimeKind.Local) + { + // For normal time zones, the ambiguous hour is the last hour of daylight saving when you wind the + // clock back. It is theoretically possible to have a positive delta, (which would really be daylight + // reduction time), where you would have to wind the clock back in the begnning. + if (GetIsAmbiguousTime(time, rule, daylightTime)) + { + isDst = time.IsAmbiguousDaylightSavingTime(); + } + } + + return isDst; + } + + /// + /// Gets the offset that should be used to calculate DST start times from a UTC time. + /// + private TimeSpan GetDaylightSavingsStartOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule, int? ruleIndex) + { + if (rule.NoDaylightTransitions) + { + // use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule + AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule, ruleIndex); + return baseUtcOffset + previousRule.BaseUtcOffsetDelta + previousRule.DaylightDelta; + } + else + { + return baseUtcOffset + rule.BaseUtcOffsetDelta; /* FUTURE: + rule.StandardDelta; */ + } + } + + /// + /// Gets the offset that should be used to calculate DST end times from a UTC time. + /// + private TimeSpan GetDaylightSavingsEndOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule) + { + // NOTE: even NoDaylightTransitions rules use this logic since DST ends w.r.t. the current rule + return baseUtcOffset + rule.BaseUtcOffsetDelta + rule.DaylightDelta; /* FUTURE: + rule.StandardDelta; */ + } + + /// + /// Helper function that checks if a given dateTime is in Daylight Saving Time (DST). + /// This function assumes the dateTime is in UTC and AdjustmentRule is in a different time zone. + /// + private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpan utc, AdjustmentRule rule, int? ruleIndex, out bool isAmbiguousLocalDst, TimeZoneInfo zone) + { + isAmbiguousLocalDst = false; + + if (rule == null) + { + return false; + } + + // Get the daylight changes for the year of the specified time. + DaylightTimeStruct daylightTime = zone.GetDaylightTime(year, rule, ruleIndex); + + // The start and end times represent the range of universal times that are in DST for that year. + // Within that there is an ambiguous hour, usually right at the end, but at the beginning in + // the unusual case of a negative daylight savings delta. + // We need to handle the case if the current rule has daylight saving end by the end of year. If so, we need to check if next year starts with daylight saving on + // and get the actual daylight saving end time. Here is example for such case: + // Converting the UTC datetime "12/31/2011 8:00:00 PM" to "(UTC+03:00) Moscow, St. Petersburg, Volgograd (RTZ 2)" zone. + // In 2011 the daylight saving will go through the end of the year. If we use the end of 2011 as the daylight saving end, + // that will fail the conversion because the UTC time +4 hours (3 hours for the zone UTC offset and 1 hour for daylight saving) will move us to the next year "1/1/2012 12:00 AM", + // checking against the end of 2011 will tell we are not in daylight saving which is wrong and the conversion will be off by one hour. + // Note we handle the similar case when rule year start with daylight saving and previous year end with daylight saving. + + bool ignoreYearAdjustment = false; + TimeSpan dstStartOffset = zone.GetDaylightSavingsStartOffsetFromUtc(utc, rule, ruleIndex); + DateTime startTime; + if (rule.IsStartDateMarkerForBeginningOfYear() && daylightTime.Start.Year > DateTime.MinValue.Year) + { + int? previousYearRuleIndex; + AdjustmentRule? previousYearRule = zone.GetAdjustmentRuleForTime( + new DateTime(daylightTime.Start.Year - 1, 12, 31), + out previousYearRuleIndex); + if (previousYearRule != null && previousYearRule.IsEndDateMarkerForEndOfYear()) + { + DaylightTimeStruct previousDaylightTime = zone.GetDaylightTime( + daylightTime.Start.Year - 1, + previousYearRule, + previousYearRuleIndex); + startTime = previousDaylightTime.Start - utc - previousYearRule.BaseUtcOffsetDelta; + ignoreYearAdjustment = true; + } + else + { + startTime = new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) - dstStartOffset; + } + } + else + { + startTime = daylightTime.Start - dstStartOffset; + } + + TimeSpan dstEndOffset = zone.GetDaylightSavingsEndOffsetFromUtc(utc, rule); + DateTime endTime; + if (rule.IsEndDateMarkerForEndOfYear() && daylightTime.End.Year < DateTime.MaxValue.Year) + { + int? nextYearRuleIndex; + AdjustmentRule? nextYearRule = zone.GetAdjustmentRuleForTime( + new DateTime(daylightTime.End.Year + 1, 1, 1), + out nextYearRuleIndex); + if (nextYearRule != null && nextYearRule.IsStartDateMarkerForBeginningOfYear()) + { + if (nextYearRule.IsEndDateMarkerForEndOfYear()) + { + // next year end with daylight saving on too + endTime = new DateTime(daylightTime.End.Year + 1, 12, 31) - utc - nextYearRule.BaseUtcOffsetDelta - nextYearRule.DaylightDelta; + } + else + { + DaylightTimeStruct nextdaylightTime = zone.GetDaylightTime( + daylightTime.End.Year + 1, + nextYearRule, + nextYearRuleIndex); + endTime = nextdaylightTime.End - utc - nextYearRule.BaseUtcOffsetDelta - nextYearRule.DaylightDelta; + } + ignoreYearAdjustment = true; + } + else + { + endTime = new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) - dstEndOffset; + } + } + else + { + endTime = daylightTime.End - dstEndOffset; + } + + DateTime ambiguousStart; + DateTime ambiguousEnd; + if (daylightTime.Delta.Ticks > 0) + { + ambiguousStart = endTime - daylightTime.Delta; + ambiguousEnd = endTime; + } + else + { + ambiguousStart = startTime; + ambiguousEnd = startTime - daylightTime.Delta; + } + + bool isDst = CheckIsDst(startTime, time, endTime, ignoreYearAdjustment, rule); + + // See if the resulting local time becomes ambiguous. This must be captured here or the + // DateTime will not be able to round-trip back to UTC accurately. + if (isDst) + { + isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd); + + if (!isAmbiguousLocalDst && ambiguousStart.Year != ambiguousEnd.Year) + { + // there exists an extreme corner case where the start or end period is on a year boundary and + // because of this the comparison above might have been performed for a year-early or a year-later + // than it should have been. + DateTime ambiguousStartModified; + DateTime ambiguousEndModified; + try + { + ambiguousStartModified = ambiguousStart.AddYears(1); + ambiguousEndModified = ambiguousEnd.AddYears(1); + isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd); + } + catch (ArgumentOutOfRangeException) { } + + if (!isAmbiguousLocalDst) + { + try + { + ambiguousStartModified = ambiguousStart.AddYears(-1); + ambiguousEndModified = ambiguousEnd.AddYears(-1); + isAmbiguousLocalDst = (time >= ambiguousStart && time < ambiguousEnd); + } + catch (ArgumentOutOfRangeException) { } + } + } + } + + return isDst; + } + + private static bool CheckIsDst(DateTime startTime, DateTime time, DateTime endTime, bool ignoreYearAdjustment, AdjustmentRule rule) + { + // NoDaylightTransitions AdjustmentRules should never get their year adjusted since they adjust the offset for the + // entire time period - which may be for multiple years + if (!ignoreYearAdjustment && !rule.NoDaylightTransitions) + { + int startTimeYear = startTime.Year; + int endTimeYear = endTime.Year; + + if (startTimeYear != endTimeYear) + { + endTime = endTime.AddYears(startTimeYear - endTimeYear); + } + + int timeYear = time.Year; + + if (startTimeYear != timeYear) + { + time = time.AddYears(startTimeYear - timeYear); + } + } + + if (startTime > endTime) + { + // In southern hemisphere, the daylight saving time starts later in the year, and ends in the beginning of next year. + // Note, the summer in the southern hemisphere begins late in the year. + return (time < endTime || time >= startTime); + } + else if (rule.NoDaylightTransitions) + { + // In NoDaylightTransitions AdjustmentRules, the startTime is always before the endTime, + // and both the start and end times are inclusive + return time >= startTime && time <= endTime; + } + else + { + // In northern hemisphere, the daylight saving time starts in the middle of the year. + return time >= startTime && time < endTime; + } + } + + /// + /// Returns true when the dateTime falls into an ambiguous time range. + /// + /// For example, in Pacific Standard Time on Sunday, October 29, 2006 time jumps from + /// 2AM to 1AM. This means the timeline on Sunday proceeds as follows: + /// 12AM ... [1AM ... 1:59:59AM -> 1AM ... 1:59:59AM] 2AM ... 3AM ... + /// + /// In this example, any DateTime values that fall into the [1AM - 1:59:59AM] range + /// are ambiguous; as it is unclear if these times are in Daylight Saving Time. + /// + private static bool GetIsAmbiguousTime(DateTime time, AdjustmentRule rule, DaylightTimeStruct daylightTime) + { + bool isAmbiguous = false; + if (rule == null || rule.DaylightDelta == TimeSpan.Zero) + { + return isAmbiguous; + } + + DateTime startAmbiguousTime; + DateTime endAmbiguousTime; + + // if at DST start we transition forward in time then there is an ambiguous time range at the DST end + if (rule.DaylightDelta > TimeSpan.Zero) + { + if (rule.IsEndDateMarkerForEndOfYear()) + { // year end with daylight on so there is no ambiguous time + return false; + } + startAmbiguousTime = daylightTime.End; + endAmbiguousTime = daylightTime.End - rule.DaylightDelta; /* FUTURE: + rule.StandardDelta; */ + } + else + { + if (rule.IsStartDateMarkerForBeginningOfYear()) + { // year start with daylight on so there is no ambiguous time + return false; + } + startAmbiguousTime = daylightTime.Start; + endAmbiguousTime = daylightTime.Start + rule.DaylightDelta; /* FUTURE: - rule.StandardDelta; */ + } + + isAmbiguous = (time >= endAmbiguousTime && time < startAmbiguousTime); + + if (!isAmbiguous && startAmbiguousTime.Year != endAmbiguousTime.Year) + { + // there exists an extreme corner case where the start or end period is on a year boundary and + // because of this the comparison above might have been performed for a year-early or a year-later + // than it should have been. + DateTime startModifiedAmbiguousTime; + DateTime endModifiedAmbiguousTime; + try + { + startModifiedAmbiguousTime = startAmbiguousTime.AddYears(1); + endModifiedAmbiguousTime = endAmbiguousTime.AddYears(1); + isAmbiguous = (time >= endModifiedAmbiguousTime && time < startModifiedAmbiguousTime); + } + catch (ArgumentOutOfRangeException) { } + + if (!isAmbiguous) + { + try + { + startModifiedAmbiguousTime = startAmbiguousTime.AddYears(-1); + endModifiedAmbiguousTime = endAmbiguousTime.AddYears(-1); + isAmbiguous = (time >= endModifiedAmbiguousTime && time < startModifiedAmbiguousTime); + } + catch (ArgumentOutOfRangeException) { } + } + } + return isAmbiguous; + } + + /// + /// Helper function that checks if a given DateTime is in an invalid time ("time hole") + /// A "time hole" occurs at a DST transition point when time jumps forward; + /// For example, in Pacific Standard Time on Sunday, April 2, 2006 time jumps from + /// 1:59:59.9999999 to 3AM. The time range 2AM to 2:59:59.9999999AM is the "time hole". + /// A "time hole" is not limited to only occurring at the start of DST, and may occur at + /// the end of DST as well. + /// + private static bool GetIsInvalidTime(DateTime time, AdjustmentRule rule, DaylightTimeStruct daylightTime) + { + bool isInvalid = false; + if (rule == null || rule.DaylightDelta == TimeSpan.Zero) + { + return isInvalid; + } + + DateTime startInvalidTime; + DateTime endInvalidTime; + + // if at DST start we transition forward in time then there is an ambiguous time range at the DST end + if (rule.DaylightDelta < TimeSpan.Zero) + { + // if the year ends with daylight saving on then there cannot be any time-hole's in that year. + if (rule.IsEndDateMarkerForEndOfYear()) + return false; + + startInvalidTime = daylightTime.End; + endInvalidTime = daylightTime.End - rule.DaylightDelta; /* FUTURE: + rule.StandardDelta; */ + } + else + { + // if the year starts with daylight saving on then there cannot be any time-hole's in that year. + if (rule.IsStartDateMarkerForBeginningOfYear()) + return false; + + startInvalidTime = daylightTime.Start; + endInvalidTime = daylightTime.Start + rule.DaylightDelta; /* FUTURE: - rule.StandardDelta; */ + } + + isInvalid = (time >= startInvalidTime && time < endInvalidTime); + + if (!isInvalid && startInvalidTime.Year != endInvalidTime.Year) + { + // there exists an extreme corner case where the start or end period is on a year boundary and + // because of this the comparison above might have been performed for a year-early or a year-later + // than it should have been. + DateTime startModifiedInvalidTime; + DateTime endModifiedInvalidTime; + try + { + startModifiedInvalidTime = startInvalidTime.AddYears(1); + endModifiedInvalidTime = endInvalidTime.AddYears(1); + isInvalid = (time >= startModifiedInvalidTime && time < endModifiedInvalidTime); + } + catch (ArgumentOutOfRangeException) { } + + if (!isInvalid) + { + try + { + startModifiedInvalidTime = startInvalidTime.AddYears(-1); + endModifiedInvalidTime = endInvalidTime.AddYears(-1); + isInvalid = (time >= startModifiedInvalidTime && time < endModifiedInvalidTime); + } + catch (ArgumentOutOfRangeException) { } + } + } + return isInvalid; + } + + /// + /// Helper function that calculates the UTC offset for a dateTime in a timeZone. + /// This function assumes that the dateTime is already converted into the timeZone. + /// + private static TimeSpan GetUtcOffset(DateTime time, TimeZoneInfo zone, TimeZoneInfoOptions flags) + { + TimeSpan baseOffset = zone.BaseUtcOffset; + int? ruleIndex; + AdjustmentRule? rule = zone.GetAdjustmentRuleForTime(time, out ruleIndex); + + if (rule != null) + { + baseOffset = baseOffset + rule.BaseUtcOffsetDelta; + if (rule.HasDaylightSaving) + { + DaylightTimeStruct daylightTime = zone.GetDaylightTime(time.Year, rule, ruleIndex); + bool isDaylightSavings = GetIsDaylightSavings(time, rule, daylightTime, flags); + baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */); + } + } + + return baseOffset; + } + + /// + /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone. + /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone. + /// + private static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone) + { + bool isDaylightSavings; + return GetUtcOffsetFromUtc(time, zone, out isDaylightSavings); + } + + /// + /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone. + /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone. + /// + private static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, out bool isDaylightSavings) + { + bool isAmbiguousLocalDst; + return GetUtcOffsetFromUtc(time, zone, out isDaylightSavings, out isAmbiguousLocalDst); + } + + /// + /// Helper function that calculates the UTC offset for a UTC-dateTime in a timeZone. + /// This function assumes that the dateTime is represented in UTC and has *not* already been converted into the timeZone. + /// + internal static TimeSpan GetUtcOffsetFromUtc(DateTime time, TimeZoneInfo zone, out bool isDaylightSavings, out bool isAmbiguousLocalDst) + { + isDaylightSavings = false; + isAmbiguousLocalDst = false; + TimeSpan baseOffset = zone.BaseUtcOffset; + int year; + int? ruleIndex; + AdjustmentRule? rule; + + if (time > s_maxDateOnly) + { + rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue, out ruleIndex); + year = 9999; + } + else if (time < s_minDateOnly) + { + rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue, out ruleIndex); + year = 1; + } + else + { + rule = zone.GetAdjustmentRuleForTime(time, dateTimeisUtc: true, ruleIndex: out ruleIndex); + Debug.Assert(rule == null || ruleIndex.HasValue, + "If GetAdjustmentRuleForTime returned an AdjustmentRule, ruleIndex should also be set."); + + // As we get the associated rule using the adjusted targetTime, we should use the adjusted year (targetTime.Year) too as after adding the baseOffset, + // sometimes the year value can change if the input datetime was very close to the beginning or the end of the year. Examples of such cases: + // Libya Standard Time when used with the date 2011-12-31T23:59:59.9999999Z + // "W. Australia Standard Time" used with date 2005-12-31T23:59:00.0000000Z + DateTime targetTime = time + baseOffset; + year = targetTime.Year; + } + + if (rule != null) + { + baseOffset = baseOffset + rule.BaseUtcOffsetDelta; + if (rule.HasDaylightSaving) + { + isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone._baseUtcOffset, rule, ruleIndex, out isAmbiguousLocalDst, zone); + baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */); + } + } + + return baseOffset; + } + + /// + /// Helper function that converts a year and TransitionTime into a DateTime. + /// + internal static DateTime TransitionTimeToDateTime(int year, TransitionTime transitionTime) + { + DateTime value; + DateTime timeOfDay = transitionTime.TimeOfDay; + + if (transitionTime.IsFixedDateRule) + { + // create a DateTime from the passed in year and the properties on the transitionTime + + // if the day is out of range for the month then use the last day of the month + int day = DateTime.DaysInMonth(year, transitionTime.Month); + + value = new DateTime(year, transitionTime.Month, (day < transitionTime.Day) ? day : transitionTime.Day, + timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); + } + else + { + if (transitionTime.Week <= 4) + { + // + // Get the (transitionTime.Week)th Sunday. + // + value = new DateTime(year, transitionTime.Month, 1, + timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); + + int dayOfWeek = (int)value.DayOfWeek; + int delta = (int)transitionTime.DayOfWeek - dayOfWeek; + if (delta < 0) + { + delta += 7; + } + delta += 7 * (transitionTime.Week - 1); + + if (delta > 0) + { + value = value.AddDays(delta); + } + } + else + { + // + // If TransitionWeek is greater than 4, we will get the last week. + // + int daysInMonth = DateTime.DaysInMonth(year, transitionTime.Month); + value = new DateTime(year, transitionTime.Month, daysInMonth, + timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); + + // This is the day of week for the last day of the month. + int dayOfWeek = (int)value.DayOfWeek; + int delta = dayOfWeek - (int)transitionTime.DayOfWeek; + if (delta < 0) + { + delta += 7; + } + + if (delta > 0) + { + value = value.AddDays(-delta); + } + } + } + return value; + } + + /// + /// Helper function for retrieving a TimeZoneInfo object by time_zone_name. + /// + /// This function may return null. + /// + /// assumes cachedData lock is taken + /// + private static TimeZoneInfoResult TryGetTimeZone(string id, bool dstDisabled, out TimeZoneInfo? value, out Exception? e, CachedData cachedData, bool alwaysFallbackToLocalMachine = false) + { + Debug.Assert(Monitor.IsEntered(cachedData)); + + TimeZoneInfoResult result = TimeZoneInfoResult.Success; + e = null; + TimeZoneInfo? match = null; + + // check the cache + if (cachedData._systemTimeZones != null) + { + if (cachedData._systemTimeZones.TryGetValue(id, out match)) + { + if (dstDisabled && match._supportsDaylightSavingTime) + { + // we found a cache hit but we want a time zone without DST and this one has DST data + value = CreateCustomTimeZone(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName); + } + else + { + value = new TimeZoneInfo(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName, + match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false); + } + return result; + } + } + + // Fall back to reading from the local machine when the cache is not fully populated. + // On UNIX, there may be some tzfiles that aren't in the zones.tab file, and thus aren't returned from GetSystemTimeZones(). + // If a caller asks for one of these zones before calling GetSystemTimeZones(), the time zone is returned successfully. But if + // GetSystemTimeZones() is called first, FindSystemTimeZoneById will throw TimeZoneNotFoundException, which is inconsistent. + // To fix this, when 'alwaysFallbackToLocalMachine' is true, even if _allSystemTimeZonesRead is true, try reading the tzfile + // from disk, but don't add the time zone to the list returned from GetSystemTimeZones(). These time zones will only be + // available if asked for directly. + if (!cachedData._allSystemTimeZonesRead || alwaysFallbackToLocalMachine) + { + result = TryGetTimeZoneFromLocalMachine(id, dstDisabled, out value, out e, cachedData); + } + else + { + result = TimeZoneInfoResult.TimeZoneNotFoundException; + value = null; + } + + return result; + } + + private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachine(string id, bool dstDisabled, out TimeZoneInfo? value, out Exception? e, CachedData cachedData) + { + TimeZoneInfoResult result; + TimeZoneInfo? match; + + result = TryGetTimeZoneFromLocalMachine(id, out match, out e); + + if (result == TimeZoneInfoResult.Success) + { + if (cachedData._systemTimeZones == null) + cachedData._systemTimeZones = new Dictionary(StringComparer.OrdinalIgnoreCase); + + cachedData._systemTimeZones.Add(id, match!); // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 + + if (dstDisabled && match!._supportsDaylightSavingTime) // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 + { + // we found a cache hit but we want a time zone without DST and this one has DST data + value = CreateCustomTimeZone(match._id, match._baseUtcOffset, match._displayName, match._standardDisplayName); + } + else + { + value = new TimeZoneInfo(match!._id, match._baseUtcOffset, match._displayName, match._standardDisplayName, // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761 + match._daylightDisplayName, match._adjustmentRules, disableDaylightSavingTime: false); + } + } + else + { + value = null; + } + + return result; + } + + /// + /// Helper function that performs all of the validation checks for the + /// factory methods and deserialization callback. + /// + private static void ValidateTimeZoneInfo(string id, TimeSpan baseUtcOffset, AdjustmentRule[]? adjustmentRules, out bool adjustmentRulesSupportDst) + { + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + + if (id.Length == 0) + { + throw new ArgumentException(SR.Format(SR.Argument_InvalidId, id), nameof(id)); + } + + if (UtcOffsetOutOfRange(baseUtcOffset)) + { + throw new ArgumentOutOfRangeException(nameof(baseUtcOffset), SR.ArgumentOutOfRange_UtcOffset); + } + + if (baseUtcOffset.Ticks % TimeSpan.TicksPerMinute != 0) + { + throw new ArgumentException(SR.Argument_TimeSpanHasSeconds, nameof(baseUtcOffset)); + } + + adjustmentRulesSupportDst = false; + + // + // "adjustmentRules" can either be null or a valid array of AdjustmentRule objects. + // A valid array is one that does not contain any null elements and all elements + // are sorted in chronological order + // + + if (adjustmentRules != null && adjustmentRules.Length != 0) + { + adjustmentRulesSupportDst = true; + AdjustmentRule? prev = null; + AdjustmentRule? current = null; + for (int i = 0; i < adjustmentRules.Length; i++) + { + prev = current; + current = adjustmentRules[i]; + + if (current == null) + { + throw new InvalidTimeZoneException(SR.Argument_AdjustmentRulesNoNulls); + } + + if (!IsValidAdjustmentRuleOffest(baseUtcOffset, current)) + { + throw new InvalidTimeZoneException(SR.ArgumentOutOfRange_UtcOffsetAndDaylightDelta); + } + + if (prev != null && current.DateStart <= prev.DateEnd) + { + // verify the rules are in chronological order and the DateStart/DateEnd do not overlap + throw new InvalidTimeZoneException(SR.Argument_AdjustmentRulesOutOfOrder); + } + } + } + } + + private static readonly TimeSpan MaxOffset = TimeSpan.FromHours(14.0); + private static readonly TimeSpan MinOffset = -MaxOffset; + + /// + /// Helper function that validates the TimeSpan is within +/- 14.0 hours + /// + internal static bool UtcOffsetOutOfRange(TimeSpan offset) => + offset < MinOffset || offset > MaxOffset; + + private static TimeSpan GetUtcOffset(TimeSpan baseUtcOffset, AdjustmentRule adjustmentRule) + { + return baseUtcOffset + + adjustmentRule.BaseUtcOffsetDelta + + (adjustmentRule.HasDaylightSaving ? adjustmentRule.DaylightDelta : TimeSpan.Zero); + } + + /// + /// Helper function that performs adjustment rule validation + /// + private static bool IsValidAdjustmentRuleOffest(TimeSpan baseUtcOffset, AdjustmentRule adjustmentRule) + { + TimeSpan utcOffset = GetUtcOffset(baseUtcOffset, adjustmentRule); + return !UtcOffsetOutOfRange(utcOffset); + } + + /// + /// Normalize adjustment rule offset so that it is within valid range + /// This method should not be called at all but is here in case something changes in the future + /// or if really old time zones are present on the OS (no combination is known at the moment) + /// + private static void NormalizeAdjustmentRuleOffset(TimeSpan baseUtcOffset, ref AdjustmentRule adjustmentRule) + { + // Certain time zones such as: + // Time Zone start date end date offset + // ----------------------------------------------------- + // America/Yakutat 0001-01-01 1867-10-18 14:41:00 + // America/Yakutat 1867-10-18 1900-08-20 14:41:00 + // America/Sitka 0001-01-01 1867-10-18 14:58:00 + // America/Sitka 1867-10-18 1900-08-20 14:58:00 + // Asia/Manila 0001-01-01 1844-12-31 -15:56:00 + // Pacific/Guam 0001-01-01 1845-01-01 -14:21:00 + // Pacific/Saipan 0001-01-01 1845-01-01 -14:21:00 + // + // have larger offset than currently supported by framework. + // If for whatever reason we find that time zone exceeding max + // offset of 14h this function will truncate it to the max valid offset. + // Updating max offset may cause problems with interacting with SQL server + // which uses SQL DATETIMEOFFSET field type which was originally designed to be + // bit-for-bit compatible with DateTimeOffset. + + TimeSpan utcOffset = GetUtcOffset(baseUtcOffset, adjustmentRule); + + // utc base offset delta increment + TimeSpan adjustment = TimeSpan.Zero; + + if (utcOffset > MaxOffset) + { + adjustment = MaxOffset - utcOffset; + } + else if (utcOffset < MinOffset) + { + adjustment = MinOffset - utcOffset; + } + + if (adjustment != TimeSpan.Zero) + { + adjustmentRule = AdjustmentRule.CreateAdjustmentRule( + adjustmentRule.DateStart, + adjustmentRule.DateEnd, + adjustmentRule.DaylightDelta, + adjustmentRule.DaylightTransitionStart, + adjustmentRule.DaylightTransitionEnd, + adjustmentRule.BaseUtcOffsetDelta + adjustment, + adjustmentRule.NoDaylightTransitions); + } + } + } +} From 6fd85d428df7fd30970d174741ba15c2c938838c Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 20 Apr 2019 13:15:49 -0700 Subject: [PATCH 12/24] Update dependencies from https://github.com/dotnet/core-setup build 20190420.01 (#24142) - Microsoft.NETCore.App - 3.0.0-preview5-27620-01 --- eng/Version.Details.xml | 4 ++-- eng/Versions.props | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 75b470254ed4..9617c478e9a4 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -19,9 +19,9 @@ https://github.com/dotnet/corefx 069f540c174aab3d2c0501d339ab49c14a2b3c19 - + https://github.com/dotnet/core-setup - 490010c3451a44e45f782a019efd736aa490f79c + b9a720984fa4d6454d1c66ae765bc1e34cb1d206 https://dev.azure.com/dnceng/internal/_git/dotnet-optimization diff --git a/eng/Versions.props b/eng/Versions.props index 5f8b13d2d61b..ec3d639f9b83 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -11,7 +11,7 @@ 1.0.0-alpha-004 4.6.0-preview5.19219.5 3.0.0-preview5.19219.5 - 3.0.0-preview5-27618-16 + 3.0.0-preview5-27620-01 99.99.99-master-20190313.3 99.99.99-master-20190313.3 From b2693453717f43bf90c47b8a87c60d92203551b7 Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Sat, 20 Apr 2019 13:18:54 -0700 Subject: [PATCH 13/24] Tighten asserts (#24124) --- src/vm/diagnosticserver.cpp | 2 +- src/vm/eventpipe.cpp | 4 ++-- src/vm/eventpipefile.cpp | 2 +- src/vm/eventpipeprotocolhelper.cpp | 4 ++-- src/vm/fastserializer.cpp | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vm/diagnosticserver.cpp b/src/vm/diagnosticserver.cpp index 4ce013139772..ba5812176ba1 100644 --- a/src/vm/diagnosticserver.cpp +++ b/src/vm/diagnosticserver.cpp @@ -20,7 +20,7 @@ static DWORD WINAPI DiagnosticsServerThread(LPVOID lpThreadParameter) { NOTHROW; GC_TRIGGERS; - MODE_ANY; + MODE_PREEMPTIVE; PRECONDITION(lpThreadParameter != nullptr); } CONTRACTL_END; diff --git a/src/vm/eventpipe.cpp b/src/vm/eventpipe.cpp index b6ec53011074..6af54ded7f61 100644 --- a/src/vm/eventpipe.cpp +++ b/src/vm/eventpipe.cpp @@ -244,7 +244,7 @@ EventPipeSessionID EventPipe::Enable( { THROWS; GC_TRIGGERS; - MODE_ANY; + MODE_PREEMPTIVE; PRECONDITION(circularBufferSizeInMB > 0); PRECONDITION(profilerSamplingRateInNanoseconds > 0); PRECONDITION(numProviders > 0 && pProviders != nullptr); @@ -276,7 +276,7 @@ EventPipeSessionID EventPipe::Enable( { THROWS; GC_TRIGGERS; - MODE_ANY; + MODE_PREEMPTIVE; PRECONDITION(pSession != nullptr); PRECONDITION(GetLock()->OwnedByCurrentThread()); } diff --git a/src/vm/eventpipefile.cpp b/src/vm/eventpipefile.cpp index ee16767a5be3..5ec266b982dc 100644 --- a/src/vm/eventpipefile.cpp +++ b/src/vm/eventpipefile.cpp @@ -15,7 +15,7 @@ EventPipeFile::EventPipeFile(StreamWriter *pStreamWriter) : FastSerializableObje { THROWS; GC_TRIGGERS; - MODE_ANY; + MODE_PREEMPTIVE; } CONTRACTL_END; diff --git a/src/vm/eventpipeprotocolhelper.cpp b/src/vm/eventpipeprotocolhelper.cpp index 8217dae22c8b..b30f479ca13d 100644 --- a/src/vm/eventpipeprotocolhelper.cpp +++ b/src/vm/eventpipeprotocolhelper.cpp @@ -73,7 +73,7 @@ void EventPipeProtocolHelper::StopTracing(IpcStream *pStream) { THROWS; GC_TRIGGERS; - MODE_ANY; + MODE_PREEMPTIVE; PRECONDITION(pStream != nullptr); } CONTRACTL_END; @@ -118,7 +118,7 @@ void EventPipeProtocolHelper::CollectTracing(IpcStream *pStream) { THROWS; GC_TRIGGERS; - MODE_ANY; + MODE_PREEMPTIVE; PRECONDITION(pStream != nullptr); } CONTRACTL_END; diff --git a/src/vm/fastserializer.cpp b/src/vm/fastserializer.cpp index 5457ee9d37ce..3330bffc2fd1 100644 --- a/src/vm/fastserializer.cpp +++ b/src/vm/fastserializer.cpp @@ -18,7 +18,7 @@ IpcStreamWriter::IpcStreamWriter(uint64_t id, IpcStream *pStream) : _pStream(pSt { NOTHROW; GC_TRIGGERS; - MODE_ANY; + MODE_PREEMPTIVE; PRECONDITION(_pStream != nullptr); } CONTRACTL_END; @@ -73,7 +73,7 @@ FileStreamWriter::FileStreamWriter(const SString &outputFilePath) { THROWS; GC_TRIGGERS; - MODE_ANY; + MODE_PREEMPTIVE; } CONTRACTL_END; m_pFileStream = new CFileStream(); @@ -126,7 +126,7 @@ FastSerializer::FastSerializer(StreamWriter *pStreamWriter) : m_pStreamWriter(pS { THROWS; GC_TRIGGERS; - MODE_ANY; + MODE_PREEMPTIVE; PRECONDITION(m_pStreamWriter != NULL); } CONTRACTL_END; @@ -268,7 +268,7 @@ void FastSerializer::WriteFileHeader() { NOTHROW; GC_NOTRIGGER; - MODE_ANY; + MODE_PREEMPTIVE; } CONTRACTL_END; From 9a7be98147755d337c3c5c1d8972afe32adaf797 Mon Sep 17 00:00:00 2001 From: Sung Yoon Whang Date: Sat, 20 Apr 2019 15:56:19 -0700 Subject: [PATCH 14/24] Eventpipe Crst ordering fix (#24101) * Adding PendingTypeLoadEntry to Crst order for EventPipeCrst * update header file --- src/inc/CrstTypes.def | 1 + src/inc/crsttypes.h | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/inc/CrstTypes.def b/src/inc/CrstTypes.def index 88bff8a44909..5ca54f14d753 100644 --- a/src/inc/CrstTypes.def +++ b/src/inc/CrstTypes.def @@ -683,6 +683,7 @@ Crst InlineTrackingMap End Crst EventPipe + AcquiredAfter PendingTypeLoadEntry AcquiredBefore ThreadIdDispenser ThreadStore DomainLocalBlock InstMethodHashTable End diff --git a/src/inc/crsttypes.h b/src/inc/crsttypes.h index b3bdb1400610..88367163efdb 100644 --- a/src/inc/crsttypes.h +++ b/src/inc/crsttypes.h @@ -221,7 +221,7 @@ int g_rgCrstLevelMap[] = 3, // CrstDynamicMT 3, // CrstDynLinkZapItems 7, // CrstEtwTypeLogHash - 18, // CrstEventPipe + 17, // CrstEventPipe 0, // CrstEventStore 0, // CrstException 7, // CrstExecuteManLock @@ -243,7 +243,7 @@ int g_rgCrstLevelMap[] = 3, // CrstInlineTrackingMap 16, // CrstInstMethodHashTable 0, // CrstInterfaceVTableMap - 18, // CrstInterop + 17, // CrstInterop 4, // CrstInteropData 12, // CrstIOThreadpoolWorker 0, // CrstIsJMCMethod @@ -276,7 +276,7 @@ int g_rgCrstLevelMap[] = 0, // CrstPatchEntryPoint 4, // CrstPEImage 0, // CrstPEImagePDBStream - 17, // CrstPendingTypeLoadEntry + 18, // CrstPendingTypeLoadEntry 0, // CrstPinHandle 0, // CrstPinnedByrefValidation 0, // CrstProfilerGCRefDataFreeList @@ -290,7 +290,7 @@ int g_rgCrstLevelMap[] = 9, // CrstReflection 9, // CrstReJITDomainTable 14, // CrstReJITGlobalRequest - 18, // CrstRemoting + 19, // CrstRemoting 3, // CrstRetThunkCache 0, // CrstRWLock 3, // CrstSavedExceptionInfo From bb40344e36c19b142969e6f29824a395c7a14921 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 20 Apr 2019 19:31:19 -0400 Subject: [PATCH 15/24] Update dependencies from https://github.com/dotnet/corefx build 20190419.11 (#24141) - Microsoft.NETCore.Platforms - 3.0.0-preview5.19219.11 - Microsoft.Private.CoreFx.NETCoreApp - 4.6.0-preview5.19219.11 --- eng/Version.Details.xml | 8 ++++---- eng/Versions.props | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 9617c478e9a4..7d5f183c3da0 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -11,13 +11,13 @@ https://github.com/dotnet/arcade 517bf671ea342965d007aa48f5bfd4926e58d582 - + https://github.com/dotnet/corefx - 069f540c174aab3d2c0501d339ab49c14a2b3c19 + c608ddaa2a024dde2510f3c1122c89ba07b4325d - + https://github.com/dotnet/corefx - 069f540c174aab3d2c0501d339ab49c14a2b3c19 + c608ddaa2a024dde2510f3c1122c89ba07b4325d https://github.com/dotnet/core-setup diff --git a/eng/Versions.props b/eng/Versions.props index ec3d639f9b83..850f86a841a7 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -9,8 +9,8 @@ true 2.5.1-beta.19179.4 1.0.0-alpha-004 - 4.6.0-preview5.19219.5 - 3.0.0-preview5.19219.5 + 4.6.0-preview5.19219.11 + 3.0.0-preview5.19219.11 3.0.0-preview5-27620-01 99.99.99-master-20190313.3 99.99.99-master-20190313.3 From 31c69e160a840adf0d1a278cb0d73fba1dad9cc2 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" Date: Sat, 20 Apr 2019 20:31:53 -0700 Subject: [PATCH 16/24] [master] Update dependencies from dnceng/internal/dotnet-optimization (#24120) - optimization.IBC.CoreCLR - 99.99.99-master-20190420.1 - optimization.PGO.CoreCLR - 99.99.99-master-20190420.1 --- eng/Version.Details.xml | 8 ++++---- eng/Versions.props | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 7d5f183c3da0..c0ee89a8fa59 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -23,13 +23,13 @@ https://github.com/dotnet/core-setup b9a720984fa4d6454d1c66ae765bc1e34cb1d206 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 4f9d0ecf2b8151859fd2bd0734af32ea59258a3d + 262f4c4cfae446577e19e7c79b43ad46ba456e56 - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization - 4f9d0ecf2b8151859fd2bd0734af32ea59258a3d + 262f4c4cfae446577e19e7c79b43ad46ba456e56 diff --git a/eng/Versions.props b/eng/Versions.props index 850f86a841a7..8ee0b5594758 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -12,8 +12,8 @@ 4.6.0-preview5.19219.11 3.0.0-preview5.19219.11 3.0.0-preview5-27620-01 - 99.99.99-master-20190313.3 - 99.99.99-master-20190313.3 + 99.99.99-master-20190420.1 + 99.99.99-master-20190420.1 From 472d840077d05336c812657353de5334e8fdccf0 Mon Sep 17 00:00:00 2001 From: dotnet-maestro-bot Date: Sat, 20 Apr 2019 20:37:21 -0700 Subject: [PATCH 17/24] Update BuildTools, CoreClr to preview4-03917-01, preview5-27618-71, respectively (master) (#24060) * Update BuildTools, CoreClr to preview4-03917-01, preview5-27618-71, respectively * Fix build errors * Disable warning with pragma and linked issue --- BuildToolsVersion.txt | 2 +- ILAsmVersion.txt | 2 +- dependencies.props | 6 +++--- .../System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs | 2 +- .../shared/System/Resources/ResourceManager.cs | 2 ++ .../shared/System/Threading/AsyncLocal.cs | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/BuildToolsVersion.txt b/BuildToolsVersion.txt index 433db8972e5b..1084e254601b 100644 --- a/BuildToolsVersion.txt +++ b/BuildToolsVersion.txt @@ -1 +1 @@ -3.0.0-preview4-03913-01 +3.0.0-preview4-03917-01 diff --git a/ILAsmVersion.txt b/ILAsmVersion.txt index a23af0061bc5..83468530fbf6 100644 --- a/ILAsmVersion.txt +++ b/ILAsmVersion.txt @@ -1 +1 @@ -3.0.0-preview5-27616-71 +3.0.0-preview5-27618-71 diff --git a/dependencies.props b/dependencies.props index adc20866bb72..32e7aad0cb8e 100644 --- a/dependencies.props +++ b/dependencies.props @@ -26,13 +26,13 @@ - 64388f5ec335146136dea340002316fed53df6a9 - c6a0e8080e10e8940af6b1d1809e79fdd8de55b7 + c759329d47d7e871483ef1747803839b31f2f31d + c759329d47d7e871483ef1747803839b31f2f31d - 3.0.0-preview5-27616-71 + 3.0.0-preview5-27618-71 2.4.1-pre.build.4059 1.0.0-beta-build0015 2.0.40 diff --git a/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs index 59e109a0431c..f49a1c9e057b 100644 --- a/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs +++ b/src/System.Private.CoreLib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs @@ -247,7 +247,7 @@ public bool Trim() // Under high pressure, release all thread locals if (log.IsEnabled()) { - foreach (KeyValuePair tlsBuckets in s_allTlsBuckets) + foreach (KeyValuePair tlsBuckets in s_allTlsBuckets) { T[]?[] buckets = tlsBuckets.Key; for (int i = 0; i < buckets.Length; i++) diff --git a/src/System.Private.CoreLib/shared/System/Resources/ResourceManager.cs b/src/System.Private.CoreLib/shared/System/Resources/ResourceManager.cs index db5f81ddcdf3..ec5877a6c7b3 100644 --- a/src/System.Private.CoreLib/shared/System/Resources/ResourceManager.cs +++ b/src/System.Private.CoreLib/shared/System/Resources/ResourceManager.cs @@ -304,7 +304,9 @@ public virtual void ReleaseAllResources() lock (localResourceSets) { +#pragma warning disable CS8619 // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/35131 foreach ((_, ResourceSet resourceSet) in localResourceSets) +#pragma warning restore CS8619 { resourceSet.Close(); } diff --git a/src/System.Private.CoreLib/shared/System/Threading/AsyncLocal.cs b/src/System.Private.CoreLib/shared/System/Threading/AsyncLocal.cs index 463289ae55ce..6ecf6c72cb02 100644 --- a/src/System.Private.CoreLib/shared/System/Threading/AsyncLocal.cs +++ b/src/System.Private.CoreLib/shared/System/Threading/AsyncLocal.cs @@ -465,7 +465,7 @@ public IAsyncLocalValueMap Set(IAsyncLocal key, object? value, bool treatNullVal { var multi = new MultiElementAsyncLocalValueMap(MultiElementAsyncLocalValueMap.MaxMultiElements); int index = 0; - foreach (KeyValuePair pair in this) + foreach (KeyValuePair pair in this) { if (!ReferenceEquals(key, pair.Key)) { From c48969221da787abaa0c6f1477ec9c864f0455d2 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Sun, 21 Apr 2019 19:59:20 +0300 Subject: [PATCH 18/24] Fixed link to .NET Core SDK (#24147) --- Documentation/building/windows-instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/building/windows-instructions.md b/Documentation/building/windows-instructions.md index bb241408291a..1852708f9053 100644 --- a/Documentation/building/windows-instructions.md +++ b/Documentation/building/windows-instructions.md @@ -88,7 +88,7 @@ Powershell version must be 3.0 or higher. This should be the case for Windows 8 ## DotNet Core SDK While not strictly needed to build or test the .NET Core repository, having the .NET Core SDK installed lets you use the dotnet.exe command to run .NET Core applications in the 'normal' way. We use this in the [Using Your Build](../workflow/UsingYourBuild.md) instructions. Visual Studio should have -installed the .NET Core SDK, but in case it did not you can get it from the [Installing the .NET Core SDK](https://www.microsoft.com/net/core) page. +installed the .NET Core SDK, but in case it did not you can get it from the [Installing the .NET Core SDK](https://dotnet.microsoft.com/download) page. ## Adding to the default PATH variable From a36bc61442d89d0b5c58b0b14e7bd3bde218f24d Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Sun, 21 Apr 2019 18:25:04 -0400 Subject: [PATCH 19/24] Fix AssemblyName cache hash and key (#24138) * Add ContextualReflection LoadWithPartialName case * Remove unnecessary MethodImplOptions.NoInlining * Remove m_assembly warning * Fix AssemblyName hash function * AssemblyNative::Load fix stackMark usage Do not use the stackMark if (ptrLoadContextBinder != NULL) * Temporarily disable DefaultContextOverrideTPA Test is failing due to a logic error. Fix is pending in https://github.com/dotnet/corefx/pull/37071 --- .../src/System/Reflection/RuntimeAssembly.cs | 2 ++ src/binder/assemblyname.cpp | 9 +++++++++ src/vm/assemblynative.cpp | 10 +++++----- tests/CoreFX/CoreFX.issues.json | 14 ++++++++++++++ .../ContextualReflection/ContextualReflection.cs | 4 +++- .../ContextualReflectionDependency.cs | 1 - 6 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs b/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs index 16dc8ec474be..a97fddc782b5 100644 --- a/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs +++ b/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs @@ -25,7 +25,9 @@ internal class RuntimeAssembly : Assembly private event ModuleResolveEventHandler _ModuleResolve; private string? m_fullname; private object? m_syncRoot; // Used to keep collectible types alive and as the syncroot for reflection.emit +#pragma warning disable 169 private IntPtr m_assembly; // slack for ptr datum on unmanaged side +#pragma warning restore 169 #endregion diff --git a/src/binder/assemblyname.cpp b/src/binder/assemblyname.cpp index 616869b3f903..bc978140b143 100644 --- a/src/binder/assemblyname.cpp +++ b/src/binder/assemblyname.cpp @@ -486,6 +486,15 @@ namespace BINDER_SPACE { dwUseIdentityFlags &= ~AssemblyIdentity::IDENTITY_FLAG_CONTENT_TYPE; } + if ((dwIncludeFlags & INCLUDE_PUBLIC_KEY_TOKEN) == 0) + { + dwUseIdentityFlags &= ~AssemblyIdentity::IDENTITY_FLAG_PUBLIC_KEY; + dwUseIdentityFlags &= ~AssemblyIdentity::IDENTITY_FLAG_PUBLIC_KEY_TOKEN; + } + if ((dwIncludeFlags & EXCLUDE_CULTURE) != 0) + { + dwUseIdentityFlags &= ~AssemblyIdentity::IDENTITY_FLAG_CULTURE; + } dwHash ^= static_cast(HashCaseInsensitive(GetSimpleName())); dwHash = _rotl(dwHash, 4); diff --git a/src/vm/assemblynative.cpp b/src/vm/assemblynative.cpp index aa830932ed5c..cc9515140885 100644 --- a/src/vm/assemblynative.cpp +++ b/src/vm/assemblynative.cpp @@ -78,18 +78,18 @@ FCIMPL6(Object*, AssemblyNative::Load, AssemblyNameBaseObject* assemblyNameUNSAF else { // Compute parent assembly - if (gc.requestingAssembly == NULL) + if (gc.requestingAssembly != NULL) { - pRefAssembly = SystemDomain::GetCallersAssembly(stackMark); + pRefAssembly = gc.requestingAssembly->GetAssembly(); } - else + else if (ptrLoadContextBinder == NULL) { - pRefAssembly = gc.requestingAssembly->GetAssembly(); + pRefAssembly = SystemDomain::GetCallersAssembly(stackMark); } if (pRefAssembly) { - pParentAssembly= pRefAssembly->GetDomainAssembly(); + pParentAssembly = pRefAssembly->GetDomainAssembly(); } } diff --git a/tests/CoreFX/CoreFX.issues.json b/tests/CoreFX/CoreFX.issues.json index b0eb61c7d44d..e8428f5a928b 100644 --- a/tests/CoreFX/CoreFX.issues.json +++ b/tests/CoreFX/CoreFX.issues.json @@ -1149,6 +1149,20 @@ ] } }, + { + "name": "System.Runtime.Loader.DefaultContext.Tests", + "enabled": true, + "exclusions": { + "namespaces": null, + "classes": null, + "methods": [ + { + "name" : "System.Runtime.Loader.Tests.DefaultLoadContextTests.LoadInDefaultContext", + "reason" : "Waiting for https://github.com/dotnet/corefx/pull/37071" + } + ] + } + }, { "name": "System.Runtime.Loader.Tests", "enabled": true, diff --git a/tests/src/Loader/ContextualReflection/ContextualReflection.cs b/tests/src/Loader/ContextualReflection/ContextualReflection.cs index 1612c141b2d4..2c1d081db6f3 100644 --- a/tests/src/Loader/ContextualReflection/ContextualReflection.cs +++ b/tests/src/Loader/ContextualReflection/ContextualReflection.cs @@ -321,6 +321,9 @@ void TestAssemblyLoad(bool isolated) { TestAssemblyLoad(isolated, (string assemblyName) => Assembly.Load(assemblyName)); TestAssemblyLoad(isolated, (string assemblyName) => Assembly.Load(new AssemblyName(assemblyName))); +#pragma warning disable 618 + TestAssemblyLoad(isolated, (string assemblyName) => Assembly.LoadWithPartialName(assemblyName)); +#pragma warning restore 618 } void TestAssemblyLoad(bool isolated, Func assemblyLoad) @@ -748,7 +751,6 @@ public void RunTests(bool isolated) TestMockAssemblyThrows(); } - [MethodImplAttribute(MethodImplOptions.NoInlining)] public void RunTestsIsolated() { VerifyIsolationAlc(); diff --git a/tests/src/Loader/ContextualReflection/ContextualReflectionDependency.cs b/tests/src/Loader/ContextualReflection/ContextualReflectionDependency.cs index f498db49a758..933194a304ef 100644 --- a/tests/src/Loader/ContextualReflection/ContextualReflectionDependency.cs +++ b/tests/src/Loader/ContextualReflection/ContextualReflectionDependency.cs @@ -16,7 +16,6 @@ public interface IProgram Assembly alcAssembly { get; } Type alcProgramType { get; } IProgram alcProgramInstance { get; } - [MethodImplAttribute(MethodImplOptions.NoInlining)] void RunTestsIsolated(); } From a8d5756796b57d2347e31deb405db4cf1a7e055b Mon Sep 17 00:00:00 2001 From: Carol Eidt Date: Mon, 22 Apr 2019 04:04:59 -0700 Subject: [PATCH 20/24] Fix Arm64 UpperVector save/restore (#24043) * Fix Arm64 UpperVector save/restore Change the general handling of end-of-block restores so that we always have a RefPosition on which to allocate the register needed on Arm64. Fix #23885 --- src/jit/codegenarm64.cpp | 40 +++-- src/jit/lsra.cpp | 167 +++++++++++------- src/jit/lsra.h | 39 +++- src/jit/lsrabuild.cpp | 60 +++++-- src/jit/simdcodegenxarch.cpp | 3 +- .../JitBlue/GitHub_23885/GitHub_23885.cs | 104 +++++++++++ .../JitBlue/GitHub_23885/GitHub_23885.csproj | 34 ++++ 7 files changed, 345 insertions(+), 102 deletions(-) create mode 100644 tests/src/JIT/Regression/JitBlue/GitHub_23885/GitHub_23885.cs create mode 100644 tests/src/JIT/Regression/JitBlue/GitHub_23885/GitHub_23885.csproj diff --git a/src/jit/codegenarm64.cpp b/src/jit/codegenarm64.cpp index da678dc17b04..ec54b4302d19 100644 --- a/src/jit/codegenarm64.cpp +++ b/src/jit/codegenarm64.cpp @@ -4911,9 +4911,8 @@ void CodeGen::genSIMDIntrinsicSetItem(GenTreeSIMD* simdNode) // When a 16-byte SIMD value is live across a call, the register allocator will use this intrinsic // to cause the upper half to be saved. It will first attempt to find another, unused, callee-save // register. If such a register cannot be found, it will save it to an available caller-save register. -// In that case, this node will be marked GTF_SPILL, which will cause genProduceReg to save the 8 byte -// value to the stack. (Note that if there are no caller-save registers available, the entire 16 byte -// value will be spilled to the stack.) +// In that case, this node will be marked GTF_SPILL, which will cause this method to save +// the upper half to the lclVar's home location. // void CodeGen::genSIMDIntrinsicUpperSave(GenTreeSIMD* simdNode) { @@ -4928,7 +4927,23 @@ void CodeGen::genSIMDIntrinsicUpperSave(GenTreeSIMD* simdNode) assert(targetReg != REG_NA); getEmitter()->emitIns_R_R_I_I(INS_mov, EA_8BYTE, targetReg, op1Reg, 0, 1); - genProduceReg(simdNode); + if ((simdNode->gtFlags & GTF_SPILL) != 0) + { + // This is not a normal spill; we'll spill it to the lclVar location. + // The localVar must have a stack home. + unsigned varNum = op1->AsLclVarCommon()->gtLclNum; + LclVarDsc* varDsc = compiler->lvaGetDesc(varNum); + assert(varDsc->lvOnFrame); + // We want to store this to the upper 8 bytes of this localVar's home. + int offset = 8; + + emitAttr attr = emitTypeSize(TYP_SIMD8); + getEmitter()->emitIns_S_R(INS_str, attr, targetReg, varNum, offset); + } + else + { + genProduceReg(simdNode); + } } //----------------------------------------------------------------------------- @@ -4946,11 +4961,7 @@ void CodeGen::genSIMDIntrinsicUpperSave(GenTreeSIMD* simdNode) // have their home register, this node has its targetReg on the lclVar child, and its source // on the simdNode. // Regarding spill, please see the note above on genSIMDIntrinsicUpperSave. If we have spilled -// an upper-half to a caller save register, this node will be marked GTF_SPILLED. However, unlike -// most spill scenarios, the saved tree will be different from the restored tree, but the spill -// restore logic, which is triggered by the call to genConsumeReg, requires us to provide the -// spilled tree (saveNode) in order to perform the reload. We can easily find that tree, -// as it is in the spill descriptor for the register from which it was saved. +// an upper-half to the lclVar's home location, this node will be marked GTF_SPILLED. // void CodeGen::genSIMDIntrinsicUpperRestore(GenTreeSIMD* simdNode) { @@ -4966,9 +4977,14 @@ void CodeGen::genSIMDIntrinsicUpperRestore(GenTreeSIMD* simdNode) assert(srcReg != REG_NA); if (simdNode->gtFlags & GTF_SPILLED) { - GenTree* saveNode = regSet.rsSpillDesc[srcReg]->spillTree; - noway_assert(saveNode != nullptr && (saveNode->gtRegNum == srcReg)); - genConsumeReg(saveNode); + // The localVar must have a stack home. + LclVarDsc* varDsc = compiler->lvaGetDesc(varNum); + assert(varDsc->lvOnFrame); + // We will load this from the upper 8 bytes of this localVar's home. + int offset = 8; + + emitAttr attr = emitTypeSize(TYP_SIMD8); + getEmitter()->emitIns_R_S(INS_ldr, attr, srcReg, varNum, offset); } getEmitter()->emitIns_R_R_I_I(INS_mov, EA_8BYTE, lclVarReg, srcReg, 1, 0); } diff --git a/src/jit/lsra.cpp b/src/jit/lsra.cpp index 2e431aad6a8c..500b6ff279ee 100644 --- a/src/jit/lsra.cpp +++ b/src/jit/lsra.cpp @@ -3331,7 +3331,7 @@ bool LinearScan::isRegInUse(RegRecord* regRec, RefPosition* refPosition) // because we'll always find something with a later nextLocation, but it can happen in stress when // we have LSRA_SELECT_NEAREST. if ((nextAssignedRef != nullptr) && isRefPositionActive(nextAssignedRef, refPosition->nodeLocation) && - nextAssignedRef->RequiresRegister()) + !nextAssignedRef->RegOptional()) { return true; } @@ -3693,21 +3693,21 @@ regNumber LinearScan::allocateBusyReg(Interval* current, RefPosition* refPositio { if (nextRefPosition2 != nullptr) { - assert(!nextRefPosition->RequiresRegister() || !nextRefPosition2->RequiresRegister()); + assert(nextRefPosition->RegOptional() || nextRefPosition2->RegOptional()); } else { - assert(!nextRefPosition->RequiresRegister()); + assert(nextRefPosition->RegOptional()); } } else { - assert(nextRefPosition2 != nullptr && !nextRefPosition2->RequiresRegister()); + assert(nextRefPosition2 != nullptr && nextRefPosition2->RegOptional()); } #else // !_TARGET_ARM_ Interval* assignedInterval = farthestRefPhysRegRecord->assignedInterval; RefPosition* nextRefPosition = assignedInterval->getNextRefPosition(); - assert(!nextRefPosition->RequiresRegister()); + assert(nextRefPosition->RegOptional()); #endif // !_TARGET_ARM_ } } @@ -4020,7 +4020,7 @@ void LinearScan::spillInterval(Interval* interval, RefPosition* fromRefPosition, { // If not allocated a register, Lcl var def/use ref positions even if reg optional // should be marked as spillAfter. - if (!fromRefPosition->RequiresRegister() && !(interval->isLocalVar && fromRefPosition->IsActualRef())) + if (fromRefPosition->RegOptional() && !(interval->isLocalVar && fromRefPosition->IsActualRef())) { fromRefPosition->registerAssignment = RBM_NONE; } @@ -4952,17 +4952,8 @@ void LinearScan::processBlockEndLocations(BasicBlock* currentBlock) outVarToRegMap[varIndex] = REG_STK; } #if FEATURE_PARTIAL_SIMD_CALLEE_SAVE - // Restore any partially-spilled large vector locals. - if (varTypeNeedsPartialCalleeSave(interval->registerType) && interval->isPartiallySpilled) - { - Interval* upperInterval = getUpperVectorInterval(varIndex); - if (interval->isActive && allocationPassComplete) - { - insertUpperVectorRestore(nullptr, upperInterval, currentBlock); - } - upperInterval->isActive = false; - interval->isPartiallySpilled = false; - } + // Ensure that we have no partially-spilled large vector locals. + assert(!varTypeNeedsPartialCalleeSave(interval->registerType) || !interval->isPartiallySpilled); #endif // FEATURE_PARTIAL_SIMD_CALLEE_SAVE } INDEBUG(dumpLsraAllocationEvent(LSRA_EVENT_END_BB)); @@ -5678,10 +5669,20 @@ void LinearScan::allocateRegisters() allocateReg = false; } +#if FEATURE_PARTIAL_SIMD_CALLEE_SAVE && defined(_TARGET_XARCH_) + // We can also avoid allocating a register (in fact we don't want to) if we have + // an UpperVectorRestore on xarch where the value is on the stack. + if ((currentRefPosition->refType == RefTypeUpperVectorRestore) && (currentInterval->physReg == REG_NA)) + { + assert(currentRefPosition->regOptional); + allocateReg = false; + } +#endif + #ifdef DEBUG // Under stress mode, don't attempt to allocate a reg to - // reg optional ref position. - if (allocateReg && regOptionalNoAlloc()) + // reg optional ref position, unless it's a ParamDef. + if (allocateReg && regOptionalNoAlloc() && (currentRefPosition->refType != RefTypeParamDef)) { allocateReg = false; } @@ -5698,7 +5699,7 @@ void LinearScan::allocateRegisters() // then find a register to spill if (assignedRegister == REG_NA) { - bool isAllocatable = currentRefPosition->IsActualRef() || currentRefPosition->RegOptional(); + bool isAllocatable = currentRefPosition->IsActualRef(); #if FEATURE_PARTIAL_SIMD_CALLEE_SAVE && defined(_TARGET_ARM64_) if (currentInterval->isUpperVector) { @@ -6022,7 +6023,7 @@ void LinearScan::resolveLocalRef(BasicBlock* block, GenTree* treeNode, RefPositi if (currentRefPosition->registerAssignment == RBM_NONE) { - assert(!currentRefPosition->RequiresRegister()); + assert(currentRefPosition->RegOptional()); assert(interval->isSpilled); varDsc->lvRegNum = REG_STK; @@ -6362,19 +6363,20 @@ void LinearScan::insertCopyOrReload(BasicBlock* block, GenTree* tree, unsigned m // void LinearScan::insertUpperVectorSave(GenTree* tree, RefPosition* refPosition, - Interval* upperInterval, + Interval* upperVectorInterval, BasicBlock* block) { - Interval* lclVarInterval = upperInterval->relatedInterval; + JITDUMP("Inserting UpperVectorSave for RP #%d before %d.%s:\n", refPosition->rpNum, tree->gtTreeID, + GenTree::OpName(tree->gtOper)); + Interval* lclVarInterval = upperVectorInterval->relatedInterval; assert(lclVarInterval->isLocalVar == true); + assert(refPosition->getInterval() == upperVectorInterval); regNumber lclVarReg = lclVarInterval->physReg; if (lclVarReg == REG_NA) { return; } - Interval* upperVectorInterval = refPosition->getInterval(); - assert(upperVectorInterval->relatedInterval == lclVarInterval); LclVarDsc* varDsc = compiler->lvaTable + lclVarInterval->varNum; assert(varTypeNeedsPartialCalleeSave(varDsc->lvType)); assert((genRegMask(lclVarReg) & RBM_FLT_CALLEE_SAVED) != RBM_NONE); @@ -6406,7 +6408,7 @@ void LinearScan::insertUpperVectorSave(GenTree* tree, if (spillToMem) { simdNode->gtFlags |= GTF_SPILL; - upperVectorInterval->physReg = REG_STK; + upperVectorInterval->physReg = REG_NA; } else { @@ -6415,6 +6417,8 @@ void LinearScan::insertUpperVectorSave(GenTree* tree, } blockRange.InsertBefore(tree, LIR::SeqTree(compiler, simdNode)); + DISPTREE(simdNode); + JITDUMP("\n"); } //------------------------------------------------------------------------ @@ -6431,8 +6435,12 @@ void LinearScan::insertUpperVectorSave(GenTree* tree, // In the case where 'tree' is non-null, we will insert the restore just prior to // its use, in order to ensure the proper ordering. // -void LinearScan::insertUpperVectorRestore(GenTree* tree, Interval* upperVectorInterval, BasicBlock* block) +void LinearScan::insertUpperVectorRestore(GenTree* tree, + RefPosition* refPosition, + Interval* upperVectorInterval, + BasicBlock* block) { + JITDUMP("Adding UpperVectorRestore for RP #%d ", refPosition->rpNum); Interval* lclVarInterval = upperVectorInterval->relatedInterval; assert(lclVarInterval->isLocalVar == true); regNumber lclVarReg = lclVarInterval->physReg; @@ -6455,15 +6463,17 @@ void LinearScan::insertUpperVectorRestore(GenTree* tree, Interval* upperVectorIn regNumber restoreReg = upperVectorInterval->physReg; SetLsraAdded(simdNode); - if (upperVectorInterval->physReg == REG_NA) + if (restoreReg == REG_NA) { // We need a stack location for this. assert(lclVarInterval->isSpilled); - simdNode->gtFlags |= GTF_SPILLED; #ifdef _TARGET_AMD64_ + assert(refPosition->assignedReg() == REG_NA); simdNode->gtFlags |= GTF_NOREG_AT_USE; #else - assert(!"We require a register for Arm64 UpperVectorRestore"); + simdNode->gtFlags |= GTF_SPILLED; + assert(refPosition->assignedReg() != REG_NA); + restoreReg = refPosition->assignedReg(); #endif } simdNode->gtRegNum = restoreReg; @@ -6472,8 +6482,7 @@ void LinearScan::insertUpperVectorRestore(GenTree* tree, Interval* upperVectorIn JITDUMP("Adding UpperVectorRestore "); if (tree != nullptr) { - JITDUMP("after:\n"); - DISPTREE(tree); + JITDUMP("before %d.%s:\n", tree->gtTreeID, GenTree::OpName(tree->gtOper)); LIR::Use treeUse; bool foundUse = blockRange.TryGetUse(tree, &treeUse); assert(foundUse); @@ -6937,8 +6946,6 @@ void LinearScan::resolveRegisters() } else if (currentRefPosition->refType == RefTypeUpperVectorRestore) { - // The treeNode is a use of the large vector lclVar. - noway_assert(treeNode != nullptr); // Since we don't do partial restores of tree temp intervals, this must be an upperVector. Interval* interval = currentRefPosition->getInterval(); Interval* localVarInterval = interval->relatedInterval; @@ -6949,7 +6956,7 @@ void LinearScan::resolveRegisters() assert((localVarInterval->assignedReg != nullptr) && (localVarInterval->assignedReg->regNum == localVarInterval->physReg) && (localVarInterval->assignedReg->assignedInterval == localVarInterval)); - insertUpperVectorRestore(treeNode, interval, block); + insertUpperVectorRestore(treeNode, currentRefPosition, interval, block); } localVarInterval->isPartiallySpilled = false; } @@ -6962,7 +6969,8 @@ void LinearScan::resolveRegisters() // This is either a use, a dead def, or a field of a struct Interval* interval = currentRefPosition->getInterval(); assert(currentRefPosition->refType == RefTypeUse || - currentRefPosition->registerAssignment == RBM_NONE || interval->isStructField); + currentRefPosition->registerAssignment == RBM_NONE || interval->isStructField || + interval->IsUpperVector()); // TODO-Review: Need to handle the case where any of the struct fields // are reloaded/spilled at this use @@ -7043,36 +7051,46 @@ void LinearScan::resolveRegisters() // If the value is reloaded or moved to a different register, we need to insert // a node to hold the register to which it should be reloaded RefPosition* nextRefPosition = currentRefPosition->nextRefPosition; - assert(nextRefPosition != nullptr); + noway_assert(nextRefPosition != nullptr); if (INDEBUG(alwaysInsertReload() ||) nextRefPosition->assignedReg() != currentRefPosition->assignedReg()) { - if (!currentRefPosition->getInterval()->isLocalVar) - { - while ((nextRefPosition != nullptr) && - (nextRefPosition->refType == RefTypeUpperVectorSave)) - { - nextRefPosition = nextRefPosition->nextRefPosition; - } - } - noway_assert(nextRefPosition != nullptr); - if (nextRefPosition->assignedReg() != REG_NA) +#if FEATURE_PARTIAL_SIMD_CALLEE_SAVE + // Note that we asserted above that this is an Interval RefPosition. + Interval* currentInterval = currentRefPosition->getInterval(); + if (!currentInterval->isUpperVector && nextRefPosition->refType == RefTypeUpperVectorSave) { - insertCopyOrReload(block, treeNode, currentRefPosition->getMultiRegIdx(), - nextRefPosition); + // The currentRefPosition is a spill of a tree temp. + // These have no associated Restore, as we always spill if the vector is + // in a register when this is encountered. + // The nextRefPosition we're interested in (where we may need to insert a + // reload or flag as GTF_NOREG_AT_USE) is the subsequent RefPosition. + assert(!currentInterval->isLocalVar); + nextRefPosition = nextRefPosition->nextRefPosition; + assert(nextRefPosition->refType != RefTypeUpperVectorSave); } - else + // UpperVector intervals may have unique assignments at each reference. + if (!currentInterval->isUpperVector) +#endif { - assert(nextRefPosition->RegOptional()); - - // In case of tree temps, if def is spilled and use didn't - // get a register, set a flag on tree node to be treated as - // contained at the point of its use. - if (currentRefPosition->spillAfter && currentRefPosition->refType == RefTypeDef && - nextRefPosition->refType == RefTypeUse) + if (nextRefPosition->assignedReg() != REG_NA) { - assert(nextRefPosition->treeNode == nullptr); - treeNode->gtFlags |= GTF_NOREG_AT_USE; + insertCopyOrReload(block, treeNode, currentRefPosition->getMultiRegIdx(), + nextRefPosition); + } + else + { + assert(nextRefPosition->RegOptional()); + + // In case of tree temps, if def is spilled and use didn't + // get a register, set a flag on tree node to be treated as + // contained at the point of its use. + if (currentRefPosition->spillAfter && currentRefPosition->refType == RefTypeDef && + nextRefPosition->refType == RefTypeUse) + { + assert(nextRefPosition->treeNode == nullptr); + treeNode->gtFlags |= GTF_NOREG_AT_USE; + } } } } @@ -9724,14 +9742,16 @@ void LinearScan::dumpRegRecordHeader() "Use, Fixd) followed by a '*' if it is a last use, and a 'D' if it is delayRegFree, and then the\n" "action taken during allocation (e.g. Alloc a new register, or Keep an existing one).\n" "The subsequent columns show the Interval occupying each register, if any, followed by 'a' if it is\n" - "active, and 'i'if it is inactive. Columns are only printed up to the last modifed register, which\n" - "may increase during allocation, in which case additional columns will appear. Registers which are\n" - "not marked modified have ---- in their column.\n\n"); + "active, a 'p' if it is a large vector that has been partially spilled, and 'i'if it is inactive.\n" + "Columns are only printed up to the last modifed register, which may increase during allocation," + "in which case additional columns will appear. \n" + "Registers which are not marked modified have ---- in their column.\n\n"); // First, determine the width of each register column (which holds a reg name in the // header, and an interval name in each subsequent row). int intervalNumberWidth = (int)log10((double)intervals.size()) + 1; - // The regColumnWidth includes the identifying character (I or V) and an 'i' or 'a' (inactive or active) + // The regColumnWidth includes the identifying character (I or V) and an 'i', 'p' or 'a' (inactive, + // partially-spilled or active) regColumnWidth = intervalNumberWidth + 2; if (regColumnWidth < 4) { @@ -9897,6 +9917,12 @@ void LinearScan::dumpRegRecords() { dumpIntervalName(interval); char activeChar = interval->isActive ? 'a' : 'i'; +#if FEATURE_PARTIAL_SIMD_CALLEE_SAVE + if (interval->isPartiallySpilled) + { + activeChar = 'p'; + } +#endif // FEATURE_PARTIAL_SIMD_CALLEE_SAVE printf("%c", activeChar); } else if (regRecord.isBusyUntilNextKill) @@ -10471,9 +10497,22 @@ void LinearScan::verifyFinalAllocation() { regRecord->assignedInterval = nullptr; } +#if FEATURE_PARTIAL_SIMD_CALLEE_SAVE + else if (interval->isUpperVector && !currentRefPosition->RegOptional()) + { + // These only require a register if they are not RegOptional, and their lclVar + // interval is living in a register and not already partially spilled. + if ((currentRefPosition->refType == RefTypeUpperVectorSave) || + (currentRefPosition->refType == RefTypeUpperVectorRestore)) + { + Interval* lclVarInterval = interval->relatedInterval; + assert((lclVarInterval->physReg == REG_NA) || lclVarInterval->isPartiallySpilled); + } + } +#endif else { - assert(!currentRefPosition->RequiresRegister()); + assert(currentRefPosition->RegOptional()); } } } diff --git a/src/jit/lsra.h b/src/jit/lsra.h index c5477bf9e125..20f93f2b39d6 100644 --- a/src/jit/lsra.h +++ b/src/jit/lsra.h @@ -656,10 +656,17 @@ class LinearScan : public LinearScanInterface #if FEATURE_PARTIAL_SIMD_CALLEE_SAVE void makeUpperVectorInterval(unsigned varIndex); Interval* getUpperVectorInterval(unsigned varIndex); + // Save the upper half of a vector that lives in a callee-save register at the point of a call. - void insertUpperVectorSave(GenTree* tree, RefPosition* refPosition, Interval* lclVarInterval, BasicBlock* block); + void insertUpperVectorSave(GenTree* tree, + RefPosition* refPosition, + Interval* upperVectorInterval, + BasicBlock* block); // Restore the upper half of a vector that's been partially spilled prior to a use in 'tree'. - void insertUpperVectorRestore(GenTree* tree, Interval* interval, BasicBlock* block); + void insertUpperVectorRestore(GenTree* tree, + RefPosition* refPosition, + Interval* upperVectorInterval, + BasicBlock* block); #endif // FEATURE_PARTIAL_SIMD_CALLEE_SAVE // resolve along one block-block edge @@ -992,6 +999,7 @@ class LinearScan : public LinearScanInterface #if FEATURE_PARTIAL_SIMD_CALLEE_SAVE void buildUpperVectorSaveRefPositions(GenTree* tree, LsraLocation currentLoc, regMaskTP fpCalleeKillSet); + void buildUpperVectorRestoreRefPosition(Interval* lclVarInterval, LsraLocation currentLoc, GenTree* node); #endif // FEATURE_PARTIAL_SIMD_CALLEE_SAVE #if defined(UNIX_AMD64_ABI) @@ -2055,12 +2063,27 @@ class RefPosition // Returns true if it is a reference on a gentree node. bool IsActualRef() { - return (refType == RefTypeDef || refType == RefTypeUse); - } - - bool RequiresRegister() - { - return IsActualRef() && !RegOptional(); + switch (refType) + { + case RefTypeDef: + case RefTypeUse: +#if FEATURE_PARTIAL_SIMD_CALLEE_SAVE + case RefTypeUpperVectorSave: + case RefTypeUpperVectorRestore: +#endif + return true; + + // These must always be marked RegOptional. + case RefTypeExpUse: + case RefTypeParamDef: + case RefTypeDummyDef: + case RefTypeZeroInit: + assert(RegOptional()); + return false; + + default: + return false; + } } void setRegOptional(bool val) diff --git a/src/jit/lsrabuild.cpp b/src/jit/lsrabuild.cpp index 1b0f61ddcca2..fe8b7678c270 100644 --- a/src/jit/lsrabuild.cpp +++ b/src/jit/lsrabuild.cpp @@ -1404,11 +1404,40 @@ void LinearScan::buildUpperVectorSaveRefPositions(GenTree* tree, LsraLocation cu { if (varTypeNeedsPartialCalleeSave(listNode->treeNode->TypeGet())) { - RefPosition* pos = newRefPosition(listNode->ref->getInterval(), currentLoc, RefTypeUpperVectorSave, tree, - RBM_FLT_CALLEE_SAVED); + // In the rare case where such an interval is live across nested calls, we don't need to insert another. + if (listNode->ref->getInterval()->recentRefPosition->refType != RefTypeUpperVectorSave) + { + RefPosition* pos = newRefPosition(listNode->ref->getInterval(), currentLoc, RefTypeUpperVectorSave, + tree, RBM_FLT_CALLEE_SAVED); + } } } } + +//------------------------------------------------------------------------ +// buildUpperVectorRestoreRefPosition - Create a RefPosition for restoring +// the upper half of a large vector. +// +// Arguments: +// lclVarInterval - A lclVarInterval that is live at 'currentLoc' +// currentLoc - The current location for which we're building RefPositions +// node - The node, if any, that the restore would be inserted before. +// If null, the restore will be inserted at the end of the block. +// +void LinearScan::buildUpperVectorRestoreRefPosition(Interval* lclVarInterval, LsraLocation currentLoc, GenTree* node) +{ + if (lclVarInterval->isPartiallySpilled) + { + unsigned varIndex = lclVarInterval->getVarIndex(compiler); + Interval* upperVectorInterval = getUpperVectorInterval(varIndex); + RefPosition* pos = newRefPosition(upperVectorInterval, currentLoc, RefTypeUpperVectorRestore, node, RBM_NONE); + lclVarInterval->isPartiallySpilled = false; +#ifdef _TARGET_XARCH_ + pos->regOptional = true; +#endif + } +} + #endif // FEATURE_PARTIAL_SIMD_CALLEE_SAVE #ifdef DEBUG @@ -1749,6 +1778,7 @@ void LinearScan::insertZeroInitRefPositions() GenTree* firstNode = getNonEmptyBlock(compiler->fgFirstBB)->firstNode(); RefPosition* pos = newRefPosition(interval, MinLocation, RefTypeZeroInit, firstNode, allRegs(interval->registerType)); + pos->setRegOptional(true); varDsc->lvMustInit = true; } else @@ -1983,6 +2013,7 @@ void LinearScan::buildIntervals() assignPhysReg(inArgReg, interval); } RefPosition* pos = newRefPosition(interval, MinLocation, RefTypeParamDef, nullptr, mask); + pos->setRegOptional(true); } else if (varTypeIsStruct(argDsc->lvType)) { @@ -1996,6 +2027,7 @@ void LinearScan::buildIntervals() Interval* interval = getIntervalForLocalVar(fieldVarDsc->lvVarIndex); RefPosition* pos = newRefPosition(interval, MinLocation, RefTypeParamDef, nullptr, allRegs(TypeGet(fieldVarDsc))); + pos->setRegOptional(true); } } } @@ -2110,6 +2142,7 @@ void LinearScan::buildIntervals() Interval* interval = getIntervalForLocalVar(varIndex); RefPosition* pos = newRefPosition(interval, currentLoc, RefTypeDummyDef, nullptr, allRegs(interval->registerType)); + pos->setRegOptional(true); } } JITDUMP("Finished creating dummy definitions\n\n"); @@ -2154,15 +2187,17 @@ void LinearScan::buildIntervals() } #if FEATURE_PARTIAL_SIMD_CALLEE_SAVE - // At the end of each block, we'll restore any upperVectors, so reset isPartiallySpilled on all of them. + // At the end of each block, create upperVectorRestores for any largeVectorVars that may be + // partiallySpilled (during the build phase all intervals will be marked isPartiallySpilled if + // they *may) be partially spilled at any point. if (enregisterLocalVars) { VarSetOps::Iter largeVectorVarsIter(compiler, largeVectorVars); unsigned largeVectorVarIndex = 0; while (largeVectorVarsIter.NextElem(&largeVectorVarIndex)) { - Interval* lclVarInterval = getIntervalForLocalVar(largeVectorVarIndex); - lclVarInterval->isPartiallySpilled = false; + Interval* lclVarInterval = getIntervalForLocalVar(largeVectorVarIndex); + buildUpperVectorRestoreRefPosition(lclVarInterval, currentLoc, nullptr); } } #endif // FEATURE_PARTIAL_SIMD_CALLEE_SAVE @@ -2230,6 +2265,7 @@ void LinearScan::buildIntervals() Interval* interval = getIntervalForLocalVar(varIndex); RefPosition* pos = newRefPosition(interval, currentLoc, RefTypeExpUse, nullptr, allRegs(interval->registerType)); + pos->setRegOptional(true); JITDUMP(" V%02u", varNum); } JITDUMP("\n"); @@ -2286,6 +2322,7 @@ void LinearScan::buildIntervals() Interval* interval = getIntervalForLocalVar(varDsc->lvVarIndex); RefPosition* pos = newRefPosition(interval, currentLoc, RefTypeExpUse, nullptr, allRegs(interval->registerType)); + pos->setRegOptional(true); } } @@ -2301,6 +2338,7 @@ void LinearScan::buildIntervals() Interval* interval = getIntervalForLocalVar(varDsc->lvVarIndex); RefPosition* pos = newRefPosition(interval, currentLoc, RefTypeExpUse, nullptr, allRegs(interval->registerType)); + pos->setRegOptional(true); } } } @@ -2633,17 +2671,7 @@ RefPosition* LinearScan::BuildUse(GenTree* operand, regMaskTP candidates, int mu VarSetOps::RemoveElemD(compiler, currentLiveVars, varIndex); } #if FEATURE_PARTIAL_SIMD_CALLEE_SAVE - if (interval->isPartiallySpilled) - { - unsigned varIndex = interval->getVarIndex(compiler); - Interval* upperVectorInterval = getUpperVectorInterval(varIndex); - RefPosition* pos = - newRefPosition(upperVectorInterval, currentLoc, RefTypeUpperVectorRestore, operand, RBM_NONE); - interval->isPartiallySpilled = false; -#ifdef _TARGET_XARCH_ - pos->regOptional = true; -#endif - } + buildUpperVectorRestoreRefPosition(interval, currentLoc, operand); #endif } else diff --git a/src/jit/simdcodegenxarch.cpp b/src/jit/simdcodegenxarch.cpp index a340fb090de5..307612dd9fb8 100644 --- a/src/jit/simdcodegenxarch.cpp +++ b/src/jit/simdcodegenxarch.cpp @@ -3086,8 +3086,7 @@ void CodeGen::genSIMDIntrinsicUpperRestore(GenTreeSIMD* simdNode) regNumber srcReg = simdNode->gtRegNum; regNumber lclVarReg = genConsumeReg(op1); assert(lclVarReg != REG_NA); - assert(srcReg != REG_NA); - if (srcReg != REG_STK) + if (srcReg != REG_NA) { getEmitter()->emitIns_R_R_R_I(INS_vinsertf128, EA_32BYTE, lclVarReg, lclVarReg, srcReg, 0x01); } diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_23885/GitHub_23885.cs b/tests/src/JIT/Regression/JitBlue/GitHub_23885/GitHub_23885.cs new file mode 100644 index 000000000000..32f4993171cc --- /dev/null +++ b/tests/src/JIT/Regression/JitBlue/GitHub_23885/GitHub_23885.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Numerics; + +public class GitHub_23885 +{ + [MethodImpl(MethodImplOptions.NoInlining)] + static void dummy(Vector v1, Vector v2, Vector v3, Vector v4, + Vector v5, Vector v6, Vector v7, Vector v8) + { + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void CreateVectors() + { + Vector vA = new Vector((ulong)0xa); + Vector vB = new Vector((ulong)0xb); + Vector vC = new Vector((ulong)0xc); + Vector vD = new Vector((ulong)0xd); + Vector vE = new Vector((ulong)0xe); + Vector vF = new Vector((ulong)0xf); + Vector vG = new Vector((ulong)0x8); + Vector vH = new Vector((ulong)0x9); + dummy(vA, vB, vC, vD, vE, vF, vG, vH); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static double GetDouble() + { + return 1.0; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void ConsumeDouble(double d) + { + } + + static int Main(string[] args) + { + int returnVal = 100; + + // Define a non-SIMD floating point value so that we've got an odd number of + // callee-save floating-point/vector registers remaining. + double d = GetDouble(); + + // Declare and initialize a bunch of vectors. + Vector xA = Vector.One; + Vector xB = Vector.One; + Vector xC = Vector.One; + Vector xD = Vector.One; + Vector xE = Vector.One; + Vector xF = Vector.One; + + // Use d a few times to give it more weight than the SIMD values. + ConsumeDouble((d * d) + d); + d = d * GetDouble(); + + // Use the vectors in computation to give them some weight. + xA = xB + xC + xD + xE + xF; + xB = xA + xC + xD + xE + xF; + xC = xA + xB + xD + xE + xF; + xD = xA + xC + xC + xE + xF; + xE = xA + xC + xD + xD + xF; + xF = xA + xC + xD + xE + xE; + + ConsumeDouble((d * d) + d); + d = d * GetDouble(); + + CreateVectors(); + + Console.WriteLine("{0} {1} {2} {3} {4} {5}", xA, xB, xC, xD, xE, xF); + if (!xA.Equals(new Vector((ulong)5)) || !xB.Equals(new Vector((ulong)9)) || !xC.Equals(new Vector((ulong)17)) || + !xD.Equals(new Vector((ulong)41)) || !xE.Equals(new Vector((ulong)105)) || !xF.Equals(new Vector((ulong)273))) + { + returnVal = -1; + } + + // Now, create more vectors, so that even more will be spilled aross the next call. + Vector xG = Vector.Zero; + Vector xH = Vector.Zero; + Vector xI = Vector.Zero; + + CreateVectors(); + + Console.WriteLine("{0} {1} {2} {3} {4} {5} {6} {7} {8}", xA, xB, xC, xD, xE, xF, xG, xH, xI); + + ConsumeDouble((d * d) + d); + d = d * GetDouble(); + + if (!xA.Equals(new Vector((ulong)5)) || !xB.Equals(new Vector((ulong)9)) || !xC.Equals(new Vector((ulong)17)) || + !xD.Equals(new Vector((ulong)41)) || !xE.Equals(new Vector((ulong)105)) || !xF.Equals(new Vector((ulong)273)) || + !xG.Equals(Vector.Zero) || !xH.Equals(Vector.Zero) || !xI.Equals(Vector.Zero)) + { + returnVal = -1; + } + + return returnVal; + } +} + diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_23885/GitHub_23885.csproj b/tests/src/JIT/Regression/JitBlue/GitHub_23885/GitHub_23885.csproj new file mode 100644 index 000000000000..e191e01217e9 --- /dev/null +++ b/tests/src/JIT/Regression/JitBlue/GitHub_23885/GitHub_23885.csproj @@ -0,0 +1,34 @@ + + + + + Debug + AnyCPU + $(MSBuildProjectName) + 2.0 + {95DFC527-4DC1-495E-97D7-E94EE1F7140D} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + + + + + + + False + + + + None + True + + + + + + + + + + From 479d7edafe8035c5e8301c8ab098835614e401e7 Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Mon, 22 Apr 2019 10:07:13 -0400 Subject: [PATCH 21/24] Remove create assembly name (#24154) * Remove RuntimeAssembly.CreateAssemblyName Fixes #24135 CreateAssemblyName was not compatible with AssemblyLoadContext isolation. Assembly.Load(string) Assembly.LoadWithPartialName(string) Activator.CreateInstance(...) * Remove unused AssemblyNameNative::Init arguments * Temp disable corefx CreateInstanceAssemblyResolve --- .../shared/System/Activator.RuntimeType.cs | 11 ++----- .../shared/System/Reflection/Assembly.cs | 19 ------------ .../src/System/Reflection/Assembly.CoreCLR.cs | 22 ++++++++++++++ .../System/Reflection/AssemblyName.CoreCLR.cs | 4 +-- .../src/System/Reflection/RuntimeAssembly.cs | 29 +------------------ src/vm/assemblyname.cpp | 17 +---------- src/vm/assemblyname.hpp | 2 +- tests/CoreFX/CoreFX.issues.json | 4 +++ 8 files changed, 34 insertions(+), 74 deletions(-) diff --git a/src/System.Private.CoreLib/shared/System/Activator.RuntimeType.cs b/src/System.Private.CoreLib/shared/System/Activator.RuntimeType.cs index cc4ef6f47137..1db47eec2247 100644 --- a/src/System.Private.CoreLib/shared/System/Activator.RuntimeType.cs +++ b/src/System.Private.CoreLib/shared/System/Activator.RuntimeType.cs @@ -112,14 +112,9 @@ public static partial class Activator } else { - RuntimeAssembly? assemblyFromResolveEvent; - AssemblyName assemblyName = RuntimeAssembly.CreateAssemblyName(assemblyString, out assemblyFromResolveEvent); - if (assemblyFromResolveEvent != null) - { - // Assembly was resolved via AssemblyResolve event - assembly = assemblyFromResolveEvent; - } - else if (assemblyName.ContentType == AssemblyContentType.WindowsRuntime) + AssemblyName assemblyName = new AssemblyName(assemblyString); + + if (assemblyName.ContentType == AssemblyContentType.WindowsRuntime) { // WinRT type - we have to use Type.GetType type = Type.GetType(typeName + ", " + assemblyString, true /*throwOnError*/, ignoreCase); diff --git a/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs b/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs index 1e9699f62f27..4722ed3fc9e7 100644 --- a/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs +++ b/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs @@ -199,25 +199,6 @@ public static Assembly GetAssembly(Type type) public static Assembly Load(byte[] rawAssembly) => Load(rawAssembly, rawSymbolStore: null); - [Obsolete("This method has been deprecated. Please use Assembly.Load() instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public static Assembly LoadWithPartialName(string partialName) - { - if (partialName == null) - throw new ArgumentNullException(nameof(partialName)); - - if ((partialName.Length == 0) || (partialName[0] == '\0')) - throw new ArgumentException(SR.Format_StringZeroLength, nameof(partialName)); - - try - { - return Load(partialName); - } - catch (FileNotFoundException) - { - return null; - } - } - // Loads the assembly with a COFF based IMAGE containing // an emitted assembly. The assembly is loaded into a fully isolated ALC with resolution fully deferred to the AssemblyLoadContext.Default. // The second parameter is the raw bytes representing the symbol store that matches the assembly. diff --git a/src/System.Private.CoreLib/src/System/Reflection/Assembly.CoreCLR.cs b/src/System.Private.CoreLib/src/System/Reflection/Assembly.CoreCLR.cs index edc0c09cf1df..8ba1ddef5835 100644 --- a/src/System.Private.CoreLib/src/System/Reflection/Assembly.CoreCLR.cs +++ b/src/System.Private.CoreLib/src/System/Reflection/Assembly.CoreCLR.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. #nullable enable +using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Loader; @@ -22,6 +23,27 @@ public static Assembly Load(string assemblyString) return RuntimeAssembly.InternalLoad(assemblyString, ref stackMark, AssemblyLoadContext.CurrentContextualReflectionContext); } + [Obsolete("This method has been deprecated. Please use Assembly.Load() instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod + public static Assembly? LoadWithPartialName(string partialName) + { + if (partialName == null) + throw new ArgumentNullException(nameof(partialName)); + + if ((partialName.Length == 0) || (partialName[0] == '\0')) + throw new ArgumentException(SR.Format_StringZeroLength, nameof(partialName)); + + try + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return RuntimeAssembly.InternalLoad(partialName, ref stackMark, AssemblyLoadContext.CurrentContextualReflectionContext); + } + catch (FileNotFoundException) + { + return null; + } + } + // Locate an assembly by its name. The name can be strong or // weak. The assembly is loaded into the domain of the caller. [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod diff --git a/src/System.Private.CoreLib/src/System/Reflection/AssemblyName.CoreCLR.cs b/src/System.Private.CoreLib/src/System/Reflection/AssemblyName.CoreCLR.cs index e6bc0b7daacc..8de0ec9b3c11 100644 --- a/src/System.Private.CoreLib/src/System/Reflection/AssemblyName.CoreCLR.cs +++ b/src/System.Private.CoreLib/src/System/Reflection/AssemblyName.CoreCLR.cs @@ -22,7 +22,7 @@ public AssemblyName(string assemblyName) throw new ArgumentException(SR.Format_StringZeroLength); _name = assemblyName; - nInit(out RuntimeAssembly? dummy, false); + nInit(); } internal AssemblyName(string? name, @@ -49,7 +49,7 @@ internal AssemblyName(string? name, } [MethodImpl(MethodImplOptions.InternalCall)] - internal extern void nInit(out RuntimeAssembly? assembly, bool raiseResolveEvent); + internal extern void nInit(); // This call opens and closes the file, but does not add the // assembly to the domain. diff --git a/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs b/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs index a97fddc782b5..cde154900eeb 100644 --- a/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs +++ b/src/System.Private.CoreLib/src/System/Reflection/RuntimeAssembly.cs @@ -306,38 +306,11 @@ public override IList GetCustomAttributesData() internal static RuntimeAssembly InternalLoad(string assemblyString, ref StackCrawlMark stackMark, AssemblyLoadContext? assemblyLoadContext = null) { - RuntimeAssembly? assembly; - AssemblyName an = CreateAssemblyName(assemblyString, out assembly); - - if (assembly != null) - { - // The assembly was returned from ResolveAssemblyEvent - return assembly; - } + AssemblyName an = new AssemblyName(assemblyString); return InternalLoadAssemblyName(an, ref stackMark, assemblyLoadContext); } - // Creates AssemblyName. Fills assembly if AssemblyResolve event has been raised. - internal static AssemblyName CreateAssemblyName( - string assemblyString, - out RuntimeAssembly? assemblyFromResolveEvent) - { - if (assemblyString == null) - throw new ArgumentNullException(nameof(assemblyString)); - - if ((assemblyString.Length == 0) || - (assemblyString[0] == '\0')) - throw new ArgumentException(SR.Format_StringZeroLength); - - AssemblyName an = new AssemblyName(); - - an.Name = assemblyString; - an.nInit(out assemblyFromResolveEvent, true); - - return an; - } - internal static RuntimeAssembly InternalLoadAssemblyName(AssemblyName assemblyRef, ref StackCrawlMark stackMark, AssemblyLoadContext? assemblyLoadContext = null) { #if FEATURE_APPX diff --git a/src/vm/assemblyname.cpp b/src/vm/assemblyname.cpp index 6c95d05c085f..ebbc485d1453 100644 --- a/src/vm/assemblyname.cpp +++ b/src/vm/assemblyname.cpp @@ -149,7 +149,7 @@ FCIMPL1(Object*, AssemblyNameNative::GetPublicKeyToken, Object* refThisUNSAFE) FCIMPLEND -FCIMPL3(void, AssemblyNameNative::Init, Object * refThisUNSAFE, OBJECTREF * pAssemblyRef, CLR_BOOL fRaiseResolveEvent) +FCIMPL1(void, AssemblyNameNative::Init, Object * refThisUNSAFE) { FCALL_CONTRACT; @@ -158,8 +158,6 @@ FCIMPL3(void, AssemblyNameNative::Init, Object * refThisUNSAFE, OBJECTREF * pAss HELPER_METHOD_FRAME_BEGIN_1(pThis); - *pAssemblyRef = NULL; - if (pThis == NULL) COMPlusThrow(kNullReferenceException, W("NullReference_This")); @@ -174,19 +172,6 @@ FCIMPL3(void, AssemblyNameNative::Init, Object * refThisUNSAFE, OBJECTREF * pAss { spec.AssemblyNameInit(&pThis,NULL); } - else if ((hr == FUSION_E_INVALID_NAME) && fRaiseResolveEvent) - { - Assembly * pAssembly = GetAppDomain()->RaiseAssemblyResolveEvent(&spec); - - if (pAssembly == NULL) - { - EEFileLoadException::Throw(&spec, hr); - } - else - { - *((OBJECTREF *) (&(*pAssemblyRef))) = pAssembly->GetExposedObject(); - } - } else { ThrowHR(hr); diff --git a/src/vm/assemblyname.hpp b/src/vm/assemblyname.hpp index 0bfb0b5d370e..41e2b27fb6e7 100644 --- a/src/vm/assemblyname.hpp +++ b/src/vm/assemblyname.hpp @@ -23,7 +23,7 @@ class AssemblyNameNative static FCDECL1(Object*, ToString, Object* refThisUNSAFE); static FCDECL1(Object*, GetPublicKeyToken, Object* refThisUNSAFE); static FCDECL1(Object*, EscapeCodeBase, StringObject* filenameUNSAFE); - static FCDECL3(void, Init, Object * refThisUNSAFE, OBJECTREF * pAssemblyRef, CLR_BOOL fRaiseResolveEvent); + static FCDECL1(void, Init, Object * refThisUNSAFE); }; #endif // _AssemblyName_H diff --git a/tests/CoreFX/CoreFX.issues.json b/tests/CoreFX/CoreFX.issues.json index e8428f5a928b..7613bebce141 100644 --- a/tests/CoreFX/CoreFX.issues.json +++ b/tests/CoreFX/CoreFX.issues.json @@ -1403,6 +1403,10 @@ "name": "System.Tests.VersionTests.Comparisons_NullArgument_ThrowsArgumentNullException", "reason": "Version was improved to no longer throw from comparison operators" }, + { + "name" : "System.Tests.ActivatorNetcoreTests.CreateInstanceAssemblyResolve", + "reason" : "Waiting for https://github.com/dotnet/corefx/pull/37080" + } ] } }, From fc6dd7009fb953d43d0912ad0755d5566868047a Mon Sep 17 00:00:00 2001 From: Russ Keldorph Date: Mon, 22 Apr 2019 05:48:33 -0700 Subject: [PATCH 22/24] Disable failing IJW tests against #23358 --- tests/issues.targets | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/issues.targets b/tests/issues.targets index e88f0b692c86..d7c5f799a360 100644 --- a/tests/issues.targets +++ b/tests/issues.targets @@ -80,6 +80,21 @@ 23941 + + 23358 + + + 23358 + + + 23358 + + + 23358 + + + 23358 + @@ -439,21 +454,6 @@ Needs triage - - Needs triage - - - Needs triage - - - Needs triage - - - Needs triage - - - Needs triage - Needs triage From 9401fa655260453168026985d3d40d3e567ef94c Mon Sep 17 00:00:00 2001 From: Sinan Kaya <41809318+franksinankaya@users.noreply.github.com> Date: Mon, 22 Apr 2019 13:09:21 -0400 Subject: [PATCH 23/24] Correct iterator (#24160) --- src/jit/codegenlinear.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jit/codegenlinear.cpp b/src/jit/codegenlinear.cpp index c2e1c74647f1..e0ecce9f2a17 100644 --- a/src/jit/codegenlinear.cpp +++ b/src/jit/codegenlinear.cpp @@ -1206,9 +1206,9 @@ void CodeGen::genNumberOperandUse(GenTree* const operand, int& useNum) const } else { - for (GenTree* operand : operand->Operands()) + for (GenTree* op : operand->Operands()) { - genNumberOperandUse(operand, useNum); + genNumberOperandUse(op, useNum); } } } From 1ae88b330ae55514fd6123a0fb9340762ab9bbb4 Mon Sep 17 00:00:00 2001 From: Steve MacLean Date: Mon, 22 Apr 2019 14:23:04 -0400 Subject: [PATCH 24/24] Fix `CORE_LIBRARIES` doc --- Documentation/workflow/UsingCoreRun.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/workflow/UsingCoreRun.md b/Documentation/workflow/UsingCoreRun.md index 8e10bb53020f..4a37a7df0017 100644 --- a/Documentation/workflow/UsingCoreRun.md +++ b/Documentation/workflow/UsingCoreRun.md @@ -19,7 +19,7 @@ It does this by looking at two environment variables. * `CORE_ROOT` - The directory where to find the runtime DLLs itself (e.g. CoreCLR.dll). Defaults to be next to the corerun.exe host itself. - * `CORE_LIBRARIES` - A Semicolon separated list of directories to look for DLLS to resolve any assembly references. + * `CORE_LIBRARIES` - A directory to look for DLLS to resolve any assembly references. It defaults CORE_ROOT if it is not specified. These simple rules can be used in a number of ways