除了"在操作系統中修改時區信息,然後重啟.NET應用程式,使其生效"之外。如何在不修改操作系統時區的前提下,修改.NET中的預設時區呢? 這是一位 同學兼同事 於5月21日在技術群里問的問題,我當時簡單地研究了一下,就寫出來了。 現在寫文章分享給大家,雖然我覺得這種需求非常小眾,幾乎不會有人用到。 ...
除了"在操作系統中修改時區信息,然後重啟.NET應用程式,使其生效"之外。如何在不修改操作系統時區的前提下,修改.NET中的預設時區呢?
這是一位 同學兼同事 於5月21日在技術群里問的問題,我當時簡單地研究了一下,就寫出來了。
現在寫文章分享給大家,雖然我覺得這種需求非常小眾,幾乎不會有人用到。
正文
正常手段下,.NET是不允許開發者修改預設時區的,它沒有公開這樣的API。
在 .NET 中,管理時區的類型叫 TimeZoneInfo
,它位於 System
命名空間下,由 System.Private.CoreLib.dll
提供。
使用 ILSpy
反編譯 System.Private.CoreLib.dll
,找到 TimeZoneInfo
類型,我們可以看到 TimeZoneInfo.Local
指向一個私有欄位 s_cachedData
的成員屬性 Local
,該欄位類型是一個屬於 TimeZoneInfo
的私有嵌套類型 CachedData
。
當首次訪問 CachedData.Local
時,它會先檢查 _localTimeZone
私有欄位是否有值。如果沒有值,則調用 CreateLocal
方法從操作系統獲取時區信息並且賦值。
看到了這裡,我腦海裡就浮現了兩種方案:
- 使用
hook
技術挾持並修改win32 api
返回的時區信息。 - 使用
reflection
技術反射並且修改時區信息。
方案1的優點是穩定,但可能會被殺毒軟體報毒。
方案2的優點是不會報毒,但可能不穩定。
為什麼說方案2不穩定呢?因為 s_cachedData 私有欄位值有可能在某個時候被重置。
現在我們來看看方案2的實現:
public static bool TrySetLocalTimeZoneInfo(TimeZoneInfo timeZoneInfo)
{
Type timeZoneInfoType = typeof(TimeZoneInfo);
// 獲取TimeZoneInfo類型的私有靜態欄位成員信息s_cachedData
FieldInfo cachedDataFieldInfo = timeZoneInfoType.GetField("s_cachedData", BindingFlags.NonPublic | BindingFlags.Static);
if (cachedDataFieldInfo == null)
{
return false;
}
// 獲取TimeZoneInfo類型的私有嵌套類型CachedData
Type cachedDataType = timeZoneInfoType.GetNestedType("CachedData", BindingFlags.NonPublic);
if (cachedDataType == null)
{
return false;
}
// 獲取CachedData類型的私有欄位成員信息_localTimeZone
FieldInfo localTimeZoneFieldInfo = cachedDataType.GetField("_localTimeZone", BindingFlags.NonPublic | BindingFlags.Instance);
if (localTimeZoneFieldInfo == null)
{
return false;
}
// 獲取TimeZoneInfo類型的私有靜態欄位s_cachedData值
object cachedData = cachedDataFieldInfo.GetValue(null);
if (cachedData == null)
{
return false;
}
// 設置私有欄位的值
localTimeZoneFieldInfo.SetValue(cachedData, timeZoneInfo);
return true;
}
PS: 該方法代碼實際測試在 .NET Core 3.1
, .NET 5.0
, .NET 6.0
, .NET 7.0
, .NET 8.0
都可以正常工作。
用法:
void Main()
{
// 設置前
Console.WriteLine(TimeZoneInfo.Local);
// 修改為 GMT 時區
TimeZoneInfo hkTimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
bool setResult = TrySetLocalTimeZoneInfo(hkTimeZoneInfo);
// 設置後
Console.WriteLine(TimeZoneInfo.Local);
}
註意:這種方案需要嚴謹測試,反覆驗證。
因為是篡改.NET內部私有變數,不知道是否會引起其它後果。
比如.NET內部其它API沒有使用 TimeZoneInfo.Local
,而是自己在其它地方又緩存了一套 TimeZoneInfo
,那就GG了。
又比如,需要檢查整個 .NET Runtime
和其它第三方組件,是否有調用 TimeZoneInfo.ClearCachedData
靜態方法 或者 調用 CultureInfo.ClearCachedData
對象方法。
出處:http://www.cnblogs.com/vallen
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
唯有偏執者得以生存。