Convert UTC to local time (and allow for daylight savings time)

I am learning how to code my first app. It's a watch face that shows tide times. I have reached a point where I can get the tide data back from a website but it's in UTC.

I would like to display it on my watch face in local time. Where I live is 10 hours ahead of UTC, so I could just add 10 hours to the UTC time, but then it will be wrong when daylight savings arrives, or if I move to a different location.

Can somebody please tell me if there is a way in monkey c to convert the UTC data that I get back into local time (also allowing for daylight savings time)?

  • I just found this post from FlowState but it's 5 years old. Is this still true?.......

    ClockTime.timeZoneOffset is actually the offset from UTC in seconds (as per the docs). This is always the case regardless of whether DST is currently in effect or not.

  • Can somebody please tell me if there is a way in monkey c to convert the UTC data that I get back into local time (also allowing for daylight savings time)?

    1) You didn't specify whether you are getting UTC time as:

    - ISO8601 format. e.g. "2025-04-10T18:21:47Z"

    - seconds since the unix epoch. e.g. "1744309307"

    - something else?

    [I assume it's ISO8601 format tho]

    Either way you want to convert it to a Moment.

    If you have a timestamp in ISO8601 format, here's some code to parse it and convert it to a moment: 

    https://forums.garmin.com/developer/connect-iq/f/discussion/410551/how-can-i-parse-timestamps-in-iso-8601-format?pifragment-1298=2#1928991

    The last line of code in that sample is:

     return Gregorian.moment(options).value();

    But for your purposes, you'll want to change it to:

     return Gregorian.moment(options);

    And you'll want to change the return type of the function to Moment

    2) Once you have a moment, use Gregorian.info() to convert it into local time. You don't have to worry about DST, it's handled automatically for you

    ClockTime.timeZoneOffset is actually the offset from UTC in seconds (as per the docs). This is always the case regardless of whether DST is currently in effect or not.

    Yeah it's true.

    For example if you live in New York and DST is not in effect, then your UTC offset is -5 hours, so timeZoneOffset should be -5 * 60 * 60.

    When DST is in effect in New York, the UTC offset is -4 hours, and in that case, timeZoneOffset would be -4 * 60 * 60.

    It doesn't matter because you don't need to use timeZoneOffset for your purpose anyway. The context of that post is that somebody wanted to use it to detect whether DST is in effect or not, but it's not really possible either way. The real problem is that ClockTime.dst is always 0 (Garmin said they wanted to deprecate it, but they apparently never got around to it.)

  • Can somebody please tell me if there is a way in monkey c to convert the UTC data that I get back into local time (also allowing for daylight savings time)?

    1) You didn't specify whether you are getting UTC time as:

    - ISO8601 format. e.g. "2025-04-10T18:21:47Z"

    - seconds since the unix epoch. e.g. "1744309307"

    - something else?

    [I assume it's ISO8601 format tho]

    Either way you want to convert it to a Moment.

    --

    If you have a timestamp in ISO8601 format, here's some code to parse it and convert it to a Moment, using Gregorian.moment() [which takes options in the form of UTC time]

    https://forums.garmin.com/developer/connect-iq/f/discussion/410551/how-can-i-parse-timestamps-in-iso-8601-format?pifragment-1298=2#1928991

    The last line of code in that sample is:

     return Gregorian.moment(options).value();

    But for your purposes, you'll want to change it to:

     return Gregorian.moment(options);

    And you'll want to change the return type of the function to Moment

    --

    If you have a timestamp in seconds since the unix epoch, it's even easier:

    var seconds = 1744309307;
    var moment = new Gregorian.Moment(seconds);

    2) Once you have a Moment, use Gregorian.info() to convert it into local time. You don't have to worry about DST, it's handled automatically for you

    ClockTime.timeZoneOffset is actually the offset from UTC in seconds (as per the docs). This is always the case regardless of whether DST is currently in effect or not.

    Yeah it's true.

    For example if you live in New York and DST is not in effect, then your UTC offset is -5 hours, so timeZoneOffset should be -5 * 60 * 60.

    When DST is in effect in New York, the UTC offset is -4 hours, and in that case, timeZoneOffset would be -4 * 60 * 60.

    It doesn't matter because you don't need to use timeZoneOffset for your purpose anyway. The context of that post is that somebody wanted to use it to detect whether DST is in effect or not, but it's not really possible either way. The real problem is that ClockTime.dst is always 0 (Garmin said they wanted to deprecate it, but they apparently never got around to it.)

  • Thanks FlowState, yes it looks like that one you called ISO8601.

    I just spent two hours figuring out how to use that timeZoneOffset feature and I got it working by converting my current time to seconds past midnight and then subtracting the offset amount, and then converting it back to a normal looking time.

    But it's awkward when the calculation ends up before midnight and becomes negative. So I'll bet your method is a lot better, but now I will have to spend another couple of hours figuring out what you wrote (for example: I don't even know what this means yet: "change the return type of the function to Moment", or how to "use Gregorian.info" to convert it into local time.)

    I will start reading.

  • I just spent two hours figuring out how to use that timeZoneOffset feature and I got it working by converting my current time to seconds past midnight and then subtracting the offset amount, and then converting it back to a normal looking time.

    But it's awkward when the calculation ends up before midnight and becomes negative.

    It also doesn't work properly if DST begins or ends between now and the time that you're trying to convert, because the time zone offset will be different.

    It's not a solution that works in general, which is why I was trying to tell you not to use it.

    So I'll bet your method is a lot better, but now I will have to spend another couple of hours figuring out what you wrote

    Sorry I wasn't clear. I meant:

    - take one of the examples in the other thread and use it to parse the ISO8601 string into a Moment. the example I linked does that, but it returns a Number after calling Moment.value(). I was saying that you shouldn't call Moment.value() for your purposes

    how to "use Gregorian.info" to convert it into local time

    - The Gregorian.info() function literally takes a Moment and gives you the corresponding local time. An example is in the linked documentation

    Here's a full example, with validation in the function that parses the ISO8601 timestamp [the other thread doesn't have validation, as the point of that thread was to implement the parsing using as little code as possible]:

    https://pastebin.com/NSb1JjiW

  • Thank you FlowState. I don't think you were unclear. It's just that I'm not a programmer so I have to learn what half the words mean that you guys use before I can understand what's happening. 

    My son came over last night and was able to figure out and explain to me what the code did in the first link you sent me yesterday (he thinks your code is brilliant, by the way).

    So now , armed with that knowledge and this last message you have sent me, I think I might be able to achieve something with another day or two of effort at the computer. 

  • I'm sorry FlowState but I need help. I've tried for 5 hours to get that code to work (i.e. print some times or "--" in the debug console) that you just posted.

    First I tried inserting it into my own program, then I gave up and just pasted the entire thing by itself into it's own file. But when I run it, the closest I've got in the debug console is below. (I'm using Visual Studio Code, if that matters?)

    Error: System.error()
    Details: Not Implemented
    Stack: 
      - getInitialView() at 778dbf13.mb:174 0x30006279 
    
    Encountered app crash.
    Background: App initialize 13:23
    Background: Counter in App initialize: 0
    Background: onStart
    Background: getServiceDelegate: 13:23
    Background: onStop
    
    From that can you possibly tell me why it isn't working please?

  • Based on that error and what you said above, it seems like you're somehow trying to run the code I gave you outside of an actual app? At the very least it seems like your app class doesn't implement getInitialView().

    Monkey C doesn't work that way - outside of unit tests, you need a full app to run code in the sim.

    That code is meant to be added to an existing app/project. You can add it as a separate file to an existing Monkey C project, but you still need the other files/classes in the project, like the app class (the class which extends AppBase) and the initial view class (returned by getInitialView()).

    Try this:

    - Create or open existing Monkey C project

    - Add the example code as a separate file

    - Somewhere in your app, call the test() function

    Once you get that working, you can see how parseISO8601() is used in the test() function, and hopefully you can start using it in your own app.

    As noted in the code, you will need to adapt momentToLocalTime() to support 12h clock. Other things you will probably have to change:

    - momentToLocalTime() returns one long string, which is probably not usable in a watch face. But the point is not to use this function directly, only to show how a Moment can be passed to Gregorian.info(), which returns local time info.

    - momentToLocalTime() doesn't pad the minutes part of the string with a leading 0. Therefore, a time like 11:05 will be incorrectly displayed as "11:5". You'll need to address this kind of issue in your own code

    Again momentToLocalTime() is meant to be an example, not to be directly used in your app.

    However, parseISO8601() should be directly usable without modification. parseISO8601() also requires parseNumber(), which it uses as a helper function.

  • Thank you again. I did spend a few hours trying to add it to my own app but I couldn't get any output at all (nothing written in the debug console, no errors ). It was as if I hadn't even added your code to my app.

    So I will try to figure out how to "call the test() function" and follow your instructions above (but first I have to go outside for a while - my head is spinning and my eyes are going funny)

    Edit: I followed your instructions, learned how to call a function, and it seems to be working. Thank you.