-
Notifications
You must be signed in to change notification settings - Fork 5.3k
[release/7.0] [Android][libs] Introduce DateTimeOffset.Now temporary fast result #74965
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
2af813a
2b068d0
1cdcdc1
188acf1
5a2197e
f4b3963
1f37efe
1b1b61a
8a928df
7e67389
f85b518
16ea40f
564f523
8f4abd0
2520363
821d87c
c0a6c24
1088724
a9034d2
eb5a67c
21231f6
e611215
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Threading; | ||
|
|
||
| namespace System | ||
| { | ||
| public readonly partial struct DateTimeOffset | ||
| { | ||
| private static bool s_androidTZDataLoaded; | ||
| private static readonly object s_localUtcOffsetLock = new(); | ||
| private static Thread? s_loadAndroidTZData; | ||
| private static bool s_startNewBackgroundThread = true; | ||
|
|
||
| // Now on Android does the following | ||
| // 1) quickly returning a fast path result when first called if the right AppContext data element is set | ||
| // 2) starting a background thread to load TimeZoneInfo local cache | ||
| // | ||
| // On Android, loading AndroidTZData is expensive for startup performance. | ||
| // The fast result relies on `System.TimeZoneInfo.LocalDateTimeOffset` being set | ||
| // in the App Context's properties as the appropriate local date time offset from UTC. | ||
| // monovm_initialize(_preparsed) can be leveraged to do so. | ||
| // However, to handle timezone changes during the app lifetime, AndroidTZData needs to be loaded. | ||
| // So, on first call, we return the fast path and start a background thread to load | ||
| // the TimeZoneInfo Local cache implementation which loads AndroidTZData. | ||
| public static DateTimeOffset Now | ||
| { | ||
| get | ||
| { | ||
| DateTime utcDateTime = DateTime.UtcNow; | ||
|
|
||
| if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded. | ||
| return ToLocalTime(utcDateTime, true); | ||
|
|
||
| if (s_startNewBackgroundThread) // The cache isn't loaded and no background thread has been created | ||
| { | ||
steveisok marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| lock (s_localUtcOffsetLock) | ||
| { | ||
| // Now may be called multiple times before a cache is loaded and a background thread is running, | ||
| // once the lock is available, check for a cache and background thread. | ||
| if (s_androidTZDataLoaded) | ||
| return ToLocalTime(utcDateTime, true); | ||
|
|
||
| if (s_loadAndroidTZData == null) | ||
| { | ||
| s_loadAndroidTZData = new Thread(() => { | ||
| // Delay the background thread to avoid impacting startup, if it still coincides after 1s, startup is already perceived as slow | ||
| Thread.Sleep(1000); | ||
|
|
||
| _ = TimeZoneInfo.Local; // Load AndroidTZData | ||
| s_androidTZDataLoaded = true; | ||
|
|
||
| lock (s_localUtcOffsetLock) | ||
| { | ||
| s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded | ||
| } | ||
|
||
| }) { IsBackground = true }; | ||
| } | ||
| } | ||
|
|
||
| if (s_startNewBackgroundThread) | ||
| { | ||
| // Because Start does not block the calling thread, | ||
| // setting the boolean flag to false immediately after should | ||
| // prevent two calls to DateTimeOffset.Now in quick succession | ||
| // from both reaching here. | ||
| // | ||
| // In the event multiple threads hit Start at the same time, | ||
| // swallow the exception and move on. | ||
steveisok marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| try | ||
stephentoub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| s_loadAndroidTZData.Start(); | ||
| s_startNewBackgroundThread = false; | ||
| } | ||
| catch | ||
| { | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset"); | ||
| if (localDateTimeOffset == null) // If no offset property provided through monovm app context, default | ||
| return ToLocalTime(utcDateTime, true); | ||
steveisok marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Fast path obtained offset incorporated into ToLocalTime(DateTime.UtcNow, true) logic | ||
| int localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset); | ||
| TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds); | ||
| long localTicks = utcDateTime.Ticks + offset.Ticks; | ||
| if (localTicks < DateTime.MinTicks || localTicks > DateTime.MaxTicks) | ||
| throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException); | ||
|
|
||
| return new DateTimeOffset(localTicks, offset); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| namespace System | ||
| { | ||
| public readonly partial struct DateTimeOffset | ||
| { | ||
| // Returns a DateTimeOffset representing the current date and time. The | ||
| // resolution of the returned value depends on the system timer. | ||
| public static DateTimeOffset Now => ToLocalTime(DateTime.UtcNow, true); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -107,7 +107,7 @@ private static int ParseGMTNumericZone(string name) | |||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| return new TimeZoneInfo(id, TimeSpan.FromSeconds(0), id, name, name, null, disableDaylightSavingTime:true); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if (name.StartsWith("GMT", StringComparison.Ordinal)) | ||||||||||||||||||||||||||||||||
| if (name.Length >= 3 && name[0] == 'G' && name[1] == 'M' && name[2] == 'T') | ||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious why this changed?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can be a perf win if we are able to delay loading ICU until after startup. This change doesn't guarantee that, it just helps.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would StartsWith("GMT", StringComparison.Ordinal) load ICU?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apparently, it's fairly easy to get to runtime/src/libraries/System.Private.CoreLib/src/System/Globalization/GlobalizationMode.Unix.cs Lines 19 to 33 in 4da3eae
|
||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||
| return new TimeZoneInfo(id, TimeSpan.FromSeconds(ParseGMTNumericZone(name)), id, name, name, null, disableDaylightSavingTime:true); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.