Skip to content

[PROTOTYPE] Remove Android time-zone change monitoring - #12247

Draft
jonathanpeppers wants to merge 3 commits into
mainfrom
jonathanpeppers-investigate-remove-timezone-monitoring
Draft

[PROTOTYPE] Remove Android time-zone change monitoring#12247
jonathanpeppers wants to merge 3 commits into
mainfrom
jonathanpeppers-investigate-remove-timezone-monitoring

Conversation

@jonathanpeppers

Copy link
Copy Markdown
Member

Draft — alternative to #12223. Do not merge without a decision on which approach we want.

#12223 implements the CoreCLR side of the // TODO: implement or remove in host-jni.cc. This PR takes the other branch of that TODO: remove the feature entirely.

Why

No other .NET platform does this. dotnet/runtime closed the equivalent request twice as by-design — #97010 (by-design, 2024-01-15) and #31043 (not planned, 2024-10-22). tarekgh, area owner:

We don't have a cheap way to listen for the TZ changes as this will require creating some listener threads. […] The apps can listen to TZ change event and then they can call TimeZoneInfo.ClearCachedData. This way we are not putting the burden on all apps.

The feature is MonoVM-only, and MonoVM is gone. src/native/clr/host/host-jni.cc was literally // TODO: implement or remove, and NativeAOT shares that host — so CoreCLR and NativeAOT have never had this. And on .NET 11+:

error NETSDK1242: Building Android projects with the Mono runtime is not supported
in .NET 11.0 and later. Use the CoreCLR runtime or target .NET 10.0.

So today this machinery only functions on a configuration customers can no longer target.

Cost of keeping it: a registerReceiver binder call on every app's startup path, an extra class in every APK, an ILLink preserve entry, and a foot-gun — timezones.cc called Helpers::abort_application () if the managed method lookup failed, so a trimming regression would crash the app rather than just leave the time zone stale.

Empirical verification

Four runs on an API 36 emulator, with both apps in the same logcat as a control. Note that adb shell setprop persist.sys.timezone does not fire the broadcast — you need adb shell cmd alarm set-timezone "<zone>" after settings put global auto_time_zone 0. (This is an easy way to get a false "fix confirmed" in manual testing.)

# Config Result
1 MonoVM / net10, no app code TZ updates live — the existing mechanism does work
2 CoreCLR / net11, no app code stale forever — feature already absent
3 CoreCLR + RegisterReceiver fixed
4 CoreCLR + [BroadcastReceiver] fixed

Two things worth recording, because both contradict assumptions I started with:

  • AndroidEnvironment.NotifyTimeZoneChanged () only calls Thread.CurrentCulture.ClearCachedData (), which looks like it wouldn't touch TimeZoneInfo. It does — CultureInfo.ClearCachedData () in dotnet/runtime main calls TimeZoneInfo.ClearCachedData (), TimeZone.ResetTimeZone () and resets RegionInfo.s_currentRegionInfo. All static, so the calling thread is irrelevant. The mechanism is not broken; it is just redundant and unsupported-by-default.
  • ACTION_TIMEZONE_CHANGED is manifest-declarable (it's on Android's implicit-broadcast exception list), and being a protected system broadcast it needs no RECEIVER_EXPORTED flag on API 34+.

What an app does instead

Manifest-declared:

[BroadcastReceiver (Enabled = true, Exported = false)]
[IntentFilter (new[] { Intent.ActionTimezoneChanged })]
public class TimeZoneChangedReceiver : BroadcastReceiver
{
	public override void OnReceive (Context? context, Intent? intent)
	{
		if (intent?.Action != Intent.ActionTimezoneChanged)
			return;
		TimeZoneInfo.ClearCachedData ();
		CultureInfo.CurrentCulture.ClearCachedData ();
	}
}

Or registered at runtime:

RegisterReceiver (new TimeZoneChangedReceiver (), new IntentFilter (Intent.ActionTimezoneChanged));

Both were confirmed to restore correct TimeZoneInfo.Local on a CoreCLR/net11 app on API 36.

Changes

Area
deleted NotifyTimeZoneChanges.java, timezones.cc, PreserveTest.cs
Java Runtime.java native decl; registerReceiver in both MonoPackageManager.java variants
native host-jni.cc/.hh stub, both .map.txt exports, CMakeLists.txt, mono_android_Runtime.h
managed AndroidEnvironment.NotifyTimeZoneChanged (), ILLink preserve entry
tests MainActivityReplacement.cs, InstallAndRunTests.cs

dotnet-local.cmd build Xamarin.Android.sln → 0 errors. Verified NotifyTimeZoneChanges is absent from all five java_runtime*.jars and Java_mono_android_Runtime_notifyTimeZoneChanged is gone from the built .so export tables (llvm-nm -D). Regenerated mono_android_Runtime.h via javac -h matches the hand edit. An app built with the locally-built SDK installs and runs cleanly on device.

Open questions before this could merge

  • ABI surface. mono.android.Runtime.notifyTimeZoneChanged is public static native and the .map.txt files are export lists. Someone should confirm no partner or binding library calls it.
  • Breaking-change note. Needs a doc entry with the replacement snippet above; MonoVM/net10 apps that relied on the automatic behaviour would need to add the receiver when they move to CoreCLR.
  • If we'd rather not take a behaviour change, the fallback ranking is: keep-but-fix (land [CoreCLR] Invalidate time zone caches after device changes #12223 and have NotifyTimeZoneChanged call TimeZoneInfo.ClearCachedData () directly rather than relying on the CultureInfo transitive path) > keep as-is.

jonathanpeppers and others added 3 commits July 27, 2026 12:10
.NET for Android is the only .NET platform that watches for system
time-zone changes and invalidates managed caches. dotnet/runtime has
twice declined to do this (dotnet/runtime#31043, dotnet/runtime#97010),
on the grounds that listening for the OS event should not be a cost
imposed on every app.

Verified on an emulator (API 36):

  * MonoVM/net10.0-android: the existing mechanism works, updating
    `TimeZoneInfo.Local` live. (`CultureInfo.ClearCachedData()`
    transitively calls `TimeZoneInfo.ClearCachedData()`.)
  * CoreCLR/net11.0-android: never updates -- `host-jni.cc` is
    `// TODO: implement or remove`. NativeAOT shares that host.
  * MonoVM is not buildable at all on `net11.0-android`
    (`NETSDK1242`), so the feature only functions in a configuration
    that can no longer be targeted.

So this deletes the whole stack: the `NotifyTimeZoneChanges` Java
receiver and its registration in both `MonoPackageManager.java`
variants, the `notifyTimeZoneChanged` JNI entry point (Java
declaration, generated header, both `.map.txt` export lists, the
MonoVM `timezones.cc` implementation and the CoreCLR stub), the
managed `AndroidEnvironment.NotifyTimeZoneChanged()` and its ILLink
preserve entry.

`PreserveTest` is retargeted at `Java.Lang.Object.SetHandleOnDeserialized`,
which is still in the preserve list.

Apps that need this behaviour can do it themselves in a few lines::

    [BroadcastReceiver (Enabled = true, Exported = false)]
    [IntentFilter (new [] { Intent.ActionTimezoneChanged })]
    public class TimeZoneChangedReceiver : BroadcastReceiver
    {
        public override void OnReceive (Context? context, Intent? intent) =>
            TimeZoneInfo.ClearCachedData ();
    }

`ACTION_TIMEZONE_CHANGED` is on Android's implicit-broadcast exception
list, so manifest declaration is allowed; it is also a protected system
broadcast, so `RegisterReceiver()` needs no API-34 export flag. Both
forms were verified on device.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6e170150-3891-4cdc-afa9-b8f9d62f1cfd
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6e170150-3891-4cdc-afa9-b8f9d62f1cfd
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 6e170150-3891-4cdc-afa9-b8f9d62f1cfd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant