Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Move implementation to DateTimeOffset.Now
  • Loading branch information
mdh1418 authored and github-actions committed Sep 1, 2022
commit 564f5232db988a585df6fbcc72e04b85d23415a1
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@
<Compile Include="$(MSBuildThisFileDirectory)System\DateTime.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTimeKind.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTimeOffset.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTimeOffset.NonAndroid.cs" Condition="'$(TargetsAndroid)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTimeOffset.Android.cs" Condition="'$(TargetsAndroid)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\DayOfWeek.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DBNull.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Decimal.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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;

// TryGetNow is a helper function on Android responsible for:
// 1) quickly returning a fast path offset when first called
// 2) starting a background thread to pursue the original implementation
//
// 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 pursue the original
// implementation which eventually loads AndroidTZData.

private static TimeSpan TryGetNow()
{
if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded.
return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime);

if (!s_androidTZDataLoaded && s_loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created
{
lock (s_localUtcOffsetLock)
{
// GetLocalUtcOffset 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 TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime);

if (s_loadAndroidTZData == null)
{
s_loadAndroidTZData = new Thread(() => {
Thread.Sleep(1000);
_ = TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime);
lock (s_localUtcOffsetLock)
{
s_androidTZDataLoaded = true;
s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded
}
});
s_loadAndroidTZData.IsBackground = true;
s_loadAndroidTZData.Start();
}
}
}

object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset");
if (localDateTimeOffset == null)
return TimeZoneInfo.GetLocalUtcOffset(DateTime.UtcNow, TimeZoneInfoOptions.NoThrowOnInvalidTime); // If no offset property provided through monovm app context, default

int localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset);
TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds);
return offset;
}

public static DateTimeOffset Now
{
get
{
DateTime utcDateTime = DateTime.UtcNow;
TimeSpan offset = TryGetNow();
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
Expand Up @@ -34,7 +34,7 @@ namespace System
[StructLayout(LayoutKind.Auto)]
[Serializable]
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public readonly struct DateTimeOffset
public readonly partial struct DateTimeOffset
: IComparable,
ISpanFormattable,
IComparable<DateTimeOffset>,
Expand Down Expand Up @@ -321,10 +321,6 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se
_dateTime = _dateTime.AddMicroseconds(microsecond);
}

// 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);

public static DateTimeOffset UtcNow
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,62 +142,6 @@ private static TimeZoneInfo GetLocalTimeZoneCore()
return Utc;
}

private static TimeSpan GetCacheLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
{
CachedData cachedData = s_cachedData;
return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData);
}

private static bool s_androidTZDataLoaded;
private static object s_localUtcOffsetLock = new();
private static Thread? s_loadAndroidTZData;
// Shortcut for TimeZoneInfo.Local.GetUtcOffset
// On Android, loading AndroidTZData while obtaining cachedData.Local is expensive for startup.
// We introduce a fast result for GetLocalUtcOffset that relies on the date time offset being
// passed into monovm_initialize(_preparsed) from Java in seconds as LOCAL_DATE_TIME_OFFSET.
// However, to handle timezone changes during the app lifetime, AndroidTZData needs to be loaded.
// The fast path is initially used, and we start a background thread to get cachedData.Local
internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
{
if (s_androidTZDataLoaded) // The background thread finished, the cache is loaded.
return GetCacheLocalUtcOffset(dateTime, flags);

if (!s_androidTZDataLoaded && s_loadAndroidTZData == null) // The cache isn't loaded and no background thread has been created
{
lock (s_localUtcOffsetLock)
{
// GetLocalUtcOffset 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 GetCacheLocalUtcOffset(dateTime, flags);

if (s_loadAndroidTZData == null)
{
s_loadAndroidTZData = new Thread(() => {
Thread.Sleep(1000);
CachedData cachedData = s_cachedData;
_ = cachedData.Local;
lock (s_localUtcOffsetLock)
{
s_androidTZDataLoaded = true;
s_loadAndroidTZData = null; // Ensure thread is cleared when cache is loaded
}
});
s_loadAndroidTZData.IsBackground = true;
s_loadAndroidTZData.Start();
}
}
}

object? localDateTimeOffset = AppContext.GetData("System.TimeZoneInfo.LocalDateTimeOffset");
if (localDateTimeOffset == null)
return GetCacheLocalUtcOffset(dateTime, flags); // If no offset property provided through monovm app context, default

int localDateTimeOffsetSeconds = Convert.ToInt32(localDateTimeOffset);
TimeSpan offset = TimeSpan.FromSeconds(localDateTimeOffsetSeconds);
return offset;
}

private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id, out TimeZoneInfo? value, out Exception? e)
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,6 @@ private static TimeZoneInfo GetLocalTimeZoneCore()
return GetLocalTimeZoneFromTzFile();
}

// Shortcut for TimeZoneInfo.Local.GetUtcOffset
internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
{
CachedData cachedData = s_cachedData;
return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData);
}

private static TimeZoneInfoResult TryGetTimeZoneFromLocalMachineCore(string id, out TimeZoneInfo? value, out Exception? e)
{
value = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,6 @@ public OffsetAndRule(int year, TimeSpan offset, AdjustmentRule? rule)
}
}

// Shortcut for TimeZoneInfo.Local.GetUtcOffset
internal static TimeSpan GetLocalUtcOffset(DateTime dateTime, TimeZoneInfoOptions flags)
{
CachedData cachedData = s_cachedData;
return cachedData.Local.GetUtcOffset(dateTime, flags, cachedData);
}

/// <summary>
/// Returns a cloned array of AdjustmentRule objects
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,13 @@ public TimeSpan GetUtcOffset(DateTimeOffset dateTimeOffset) =>
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);
}

/// <summary>
/// Returns the Universal Coordinated Time (UTC) Offset for the current TimeZoneInfo instance.
/// </summary>
Expand Down