Bug 18132 - DateTime.Now returning time of old timezone after changing IOS system timezone
Summary: DateTime.Now returning time of old timezone after changing IOS system timezone
Status: ASSIGNED
Alias: None
Product: iOS
Classification: Xamarin
Component: Xamarin.iOS.dll (show other bugs)
Version: master
Hardware: All All
: Normal normal
Target Milestone: Untriaged
Assignee: Sebastien Pouliot
URL:
Depends on:
Blocks:
 
Reported: 2014-03-03 15:24 UTC by Chad Bumstead
Modified: 2015-06-22 11:29 UTC (History)
8 users (show)

See Also:
Tags:
Is this bug a regression?: ---
Last known good build:


Attachments

Description Chad Bumstead 2014-03-03 15:24:53 UTC
DateTime.Now caches the offset for one minute (see below).   So while it is cached, Date.Now will display an incorrect time.

There should be a way to clear the DateTime.Now cache such as DateTime.ClearOffsetCache().
In addition, the Xamarin.IOS framework could utilize the AppDelegate.ApplicationSignificantTimeChange() method (which is called when timezone is changed, daylight savings time change, midnight) to call the DateTime.ClearOffsetCache() method.


https://bugzilla.xamarin.com/show_bug.cgi?id=9418#c4

This is the source for DateTime.Now:

//
// To reduce the time consumed by DateTime.Now, we keep
// the difference to map the system time into a local
// time into `to_local_time_span', we record the timestamp
// for this in `last_now'
//
static object to_local_time_span_object;
static long last_now;

public static DateTime Now {
get {
long now = GetNow ();
DateTime dt = new DateTime (now);
Comment 2 Ram Chandra 2014-03-04 06:20:45 UTC
I have checked this issue  and i am able to reproduce it.

Steps to reproduce :

(1) Create an "iOS application".
(2) Paste the following code in "AppDelegate.cs" class
.
			
                        DateTime dt1 = DateTime.Now;
			
                        DateTime dt2 = DateTime.Now;


(3) Open "watch window" in "Xamarin Studio" . 
(4) Place a breakpoint on "dt1" variable and  watch the value of  "dt1" variable.
(5) Change the "time zone" when app is running 
(6) Check the value of "dt2" variable.

screencast: http://www.screencast.com/t/SBpaw31C4gte

Environment:

Mac
Xamarin Studio: 4.2.3 (build 59)
Xamarin.iOS: 7.0.7.2
Comment 4 Sebastien Pouliot 2014-03-05 21:02:13 UTC
We won't add a public* API like `DateTime.ClearOffsetCache()` to do this - but we do have plans to fix this. 

The "right" solution is  a bit more complicated since it affects many bits, e.g. DateTime, TimeZone[Info]…, to continuously (and transparently) match the device.

* OTOH you can easily do the same feature in your application by using this code (and it will not crash/fail when we fix this), e.g.

		void ResetDateTimeCache ()
		{
			var f = typeof(DateTime).GetField ("last_now", BindingFlags.NonPublic | BindingFlags.Static);
			if (f == null)
				return; // we fixed this
			f.SetValue (null, long.MinValue);
		}

	Console.WriteLine ("1. {0}", DateTime.Now);
	ResetDateTimeCache ();
	Console.WriteLine ("2. {0}", DateTime.Now);

Inside XS you can add a debugger watch for `DateTime.last_now` and you'll see it (re)assigned which will make `to_local_time_span_object` be recomputed the next time `Now` is called (and/or you can put a breakpoint inside DateTime.cs).
Comment 5 Chad Bumstead 2014-03-10 14:38:26 UTC
Thank you Sebastien!  What would we do without reflection?

The above was a partial solution but we also need to set 'TimeZone.timezone_check' to a min value.
When I run the two methods below together DateTime.Now will give me a value that matches the IOS system time.
I override the AppDelegate ApplicationSignificantTimeChange method and reset the caches there.

    public static class DateTimeUtil
    {  
        public static void ResetDateTimeCache()
        {
            var f = typeof(DateTime).GetField ("last_now", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);

            if (f == null)
                return;
            f.SetValue (null, long.MinValue);
        }

        public static void ResetTimeZoneCache()
        {
            var f = typeof(TimeZone).GetField ("timezone_check", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);

            if (f == null)
                return;

            f.SetValue (null, long.MinValue);
        }  
    }  

    [Register ("AppDelegate")]
    public partial class AppDelegate : UIApplicationDelegate
    {  
        public override void ApplicationSignificantTimeChange(UIApplication application)
        {

            DateTimeUtil.ResetDateTimeCache ();
            DateTimeUtil.ResetTimeZoneCache ();

        }  
    }
Comment 6 Nate Cook 2015-06-08 13:00:03 UTC
This workaround doesn't seem to work anymore. (f == null is true). Is this fixed or is there another workaround?
Comment 7 Brendan Zagaeski (Xamarin Support) 2015-06-19 19:50:06 UTC
This problem has changed noticeably in Xamarin.iOS 8.10 due to the import of https://github.com/Microsoft/referencesource into Mono.


See for example `DateTime.Now`:

https://github.com/mono/referencesource/blob/ad2788873af4d5f149f83bf400507ad3eb49bffd/mscorlib/system/datetime.cs#L944



From my tests, it appears the behavior of `DateTime.Now` now matches the behavior in .NET [1].

> [1] http://stackoverflow.com/questions/296918/net-datetime-now-returns-incorrect-time-when-time-zone-is-changed



In particular, calling `System.Globalization.CultureInfo.CurrentCulture.ClearCachedData()` between calls to `DateTime.Now` produces the desired outcome in the test scenario from Comment 2.

I believe this bug can be marked as resolved fixed.


-Brendan
Xamarin Customer Support
Comment 8 Nate Cook 2015-06-22 11:29:31 UTC
It looks like that was the key. System.Globalization.CultureInfo.CurrentCulture.ClearCachedData() seems to do the trick.

Note You need to log in before you can comment on or make changes to this bug.