Handling Time Zones

Hi,

     I have a number of widgets that look to the History Iterator for data and display the results. I recently had feedback from some users in Australia that the data is one day out in the display. 

    The code below is the section where I look to set the time frame for the data, The only element that I can see which would influence the day would be the first line using Time.now() if this is not offset with the time zone or possibly the way moments are calculated.

     I am unable to replicate the issue on either watch or simulator so this one has me a little stumped. Any advice or help from your guys would be appreciated 

//      Set starting Point moment for recovery of historical data

        var CalenderInfoShort   = Calendar.info(Time.now(), Time.FORMAT_SHORT);
        var options = {
                :year         => CalenderInfoShort.year,
                :month      => CalenderInfoShort.month,
                :day          => CalenderInfoShort.day,
                :hour         => 23,
                :minute     => 59,
                :seconds   => 59,
        };

//      Determine Duration of data to collect
        DataSet["dayDataRange"] = CalenderInfoShort.day_of_week - Properties["StartOfWeekOffset"];
        if (DataSet["dayDataRange"] < 0) {DataSet["dayDataRange"] = DataSet["dayDataRange"] + 7;}
        DataSet["dayDataRange"] = DataSet["dayDataRange"] + 1;
        var momentNow   = Calendar.moment(options); // Set start DT as 23:59:59 on curremt Date
        var oneWeek     = new Time.Duration(Calendar.SECONDS_PER_DAY*-DataSet["dayDataRange"]); // Set 1 week in seconds
        var sampleStart = momentNow.add(oneWeek); // Determine start date value for comparison
       
//      Check timestamp to determine if Garmin/FIT vs Unix epoch difference
        var nowValue = Time.now().value();
        var sampleStartTimeValue = sample.startTime.value();
        var TimeOffset = 0;
        if (nowValue - sampleStartTimeValue >= 599184000) { // add ~20 years offset to account for Garmin/FIT vs Unix epoch difference
                TimeOffset = 631065600; }
  • As per the docs, Gregorian.moment() (*) interprets its input as UTC time (not what you want), whereas Gregorian.info()'s output is assumed to be in local time (which is what you want).

    The use of Gregorian.moment() explains the discrepancy, and I think you should be able to recreate the problem if you change the time zone (and possibly the time) on your development PC and test in the sim.

    While there is a localMoment() function, it requires a location to be passed in, so it isn't ideal if all you want is a moment for some calendar date in the local time zone. (It's not available for old devices and you may not have access to the current location.)

    (* For the benefit of anyone else reading, it seems that you're "using Toybox.Time.Gregorian as Calendar".)

    It seems like you are trying to determine the moment value for today at 23:59:59, for the purpose of comparing that value (plus one week) to activity timestamps.

    Here's a different way of doing that.

    var momentNow = Time.now();
    var calendarInfoShort = Calendar.info(momentNow, Time.FORMAT_SHORT);
    
    var options = {
        :year => calendarInfoShort.year,
        :month => calendarInfoShort.month,
        :day => calendarInfoShort.day,
        :hour => calendarInfoShort.hour,
        :minute => calendarInfoShort.min,
        :seconds => calendarInfoShort.sec,
    };
    // a moment for the current date and time *as if the local time zone was UTC*
    var momentUtcNow = Calendar.moment(options);
    
    options = {
        :year => options.year,
        :month => options.month,
        :day => options.day,
        :hour => 23,
        :minute => 59,
        :seconds => 59,
    };
    // a moment for the end of day *as if local time zone was UTC*
    var momentUtcEndOfDay = Calendar.moment(options);
    
    var timeToEndOfDay = momentUtcEndOfDay.subtract(momentUtcNow);
    
    // a moment for the end of day (local time)
    var momentEndOfDay = momentNow.add(timeToEndOfDay);

    The idea is if you just need to calculate the difference between 2 calendar date/times, the actual time zone doesn't matter, so you can use Gregorian.moment() as long as you're only comparing the difference between returned values (and not using them for something else.)

  • This is how convert the UTC time to the local time

    var timeZoneOffset = System.getClockTime().timeZoneOffset;
    var todayT = Time.now().value() + timeZoneOffset;
    

  • Not quite as you also want to include System.getClockTime().dst in the calculation. The sim and real devices handle this differently

  • Not quite as you also want to include System.getClockTime().dst in the calculation

    I don't think that's the case as the dst field is always 0 (at least on real devices *) and timeZoneOffset takes daylight savings time into account, regardless. (So even if dst was correct, you wouldn't want to use it in addition to timeZoneOffset.)

    (* A Garmin employee mentioned years ago that it was slated to be removed, and there's an open bug report about the fact that it's always zero.)

    This is how convert the UTC time to the local time

    The problem the OP is trying to solve is how to get a Moment for 23:59:59 local time so it can be compared with timestamps in the activity history. I think the code snippet I posted solves that problem. but it could definitely be simplified with the use of timeZoneOffset.

    e.g.

    var momentNow = Time.now();
    var calendarInfoShort = Calendar.info(momentNow, Time.FORMAT_SHORT);
    
    var options = {
        :year => calendarInfoShort.year,
        :month => calendarInfoShort.month,
        :day => calendarInfoShort.day,
        :hour => 23,
        :minute => 59,
        :seconds => 59,
    };
    };
    // a moment for the end of day (UTC)
    var momentUtcEndOfDay = Calendar.moment(options);
    
    // a moment for the end of day (local time)
    var momentEndOfDay = momentUtcEndOfDay.subtract(System.getClockTime().timeZoneOffset);

    > var timeZoneOffset = System.getClockTime().timeZoneOffset;
    > var todayT = Time.now().value() + timeZoneOffset;

    As far as this code above goes, the OP actually has a moment that's for end of day UTC time, but they want a moment for end of day local time. Your code actually helps solve the opposite problem: someone has a moment for some local calendar date/time, but they want a moment for the same calendar date/time in UTC.

    e.g. Let's say a user is in New York and DST is not in effect, so their TZ offset is -5. Say it's currently 6:00 PM locally, which means it's 11:00 PM UTC. Adding the TZ offset of -5 gives you 1:00 PM local time and 6:00 PM UTC, which demonstrates that your code works for going from "local time" to "UTC time", but the OP needs the opposite.

  • Yes sorry I didn't post the full script 

    using Toybox.Time.Gregorian as Calendar;