Subtracting a duration from a moment

Hi,
I want to get the days of month of the last 7 days. E. g. if today is the 4th of december I need to get 3, 2, 1, 30, 29, 28, 27

Therefore I wanted to subtract a duration of one day in a loop. This website states that it is possible:https://developer.garmin.com/downloa...ybox/Time.html

Moment - Duration Moment.subtract() Moment An earlier Moment


Nevertheless, I can't get that running, since the method moment.subtract requires a moment and not a duration. Is this information in the API wrong? Is there another fancy way to get the last 7 days?
  • OK, I solved it this way:

    function getLastSevenDays() {
    var lastSevenDays = new [7];
    var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    if (isLeapYear()) {
    daysInMonth[1] = 29;
    }
    //var today = 7;
    var today = Time.Gregorian.info(Time.now(), Time.FORMAT_MEDIUM).day;
    var month = Time.Gregorian.info(Time.now(), Time.FORMAT_SHORT).month;
    month -= 2; // get the last month
    if (month == -1) { month = 11; }

    for (var i=0; i<7; i++) {
    today -= 1;
    if (today == 0) {
    today = daysInMonth[month];
    }
    lastSevenDays= today;
    }
    System.println(lastSevenDays);
    return lastSevenDays;
    }

    function isLeapYear() {
    var year = Time.Gregorian.info(Time.now(), Time.FORMAT_SHORT).year;
    if ((year % 400) == 0) { return true; }
    if ((year % 100) == 0) { return false; }
    if ((year % 4 ) == 0) { return true; }

    return false;
    }
    [/CODE]
  • Looks like a bug. You can subtract a Time.Duration, but you can't add one. I'll get something filed.
  • I would have written something similar to what you've done above, but the following does what you were wanting.

    function previous_seven_month_days(year, month, day) {
    var when = Gregorian.moment({
    :year => year,
    :month => month,
    :day => day
    });

    // convert utc back to local time zone
    when = when.add(new Time.Duration(-Sys.getClockTime().timeZoneOffset));

    var neg_one_day = new Time.Duration(-Gregorian.SECONDS_PER_DAY);

    var days = new [7];

    for (var i = 0, j = days.size(); i < j; ++i) {
    when = when.add(neg_one_day);

    var day = Gregorian.info(when, Time.FORMAT_SHORT);
    days= day.day;
    }

    return days;
    }
    [/code]

    I think that I'd have written it similar to what you've done, except I'd cut out the Gregorian.info() calls from this function and move them to the caller. Makes it possible to test and it makes it more efficient since we can get by with just one call vs the 2 (or 3) that you're using. This should be a bunch better than the above since it does 1 Gregorian.info() call vs 8 at the cost of a 12 element array and a helper function...

    const days_in_month = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];

    // function is_leap_year(year) provided elsewhere

    function previous_seven_month_days(year, month, day) {

    // offset by one so we don't index out of bounds, and another so
    // we get the previous month
    month = (month + 10) % 12;

    var days = new [7];
    for (var i = 0, j = days.size(); i < j; ++i) {

    day -= 1;

    if (day == 0) {
    if (month == 1 && is_leap_year(year)) {
    day = 29;
    }
    else {
    day = days_in_month[month];
    }
    }

    days= day;
    }

    return days;
    }
    [/code]

    Here is a test to double-check everything...

    (:test)
    module Test
    {

    function do_test_date(logger, year, month, day, expected) {
    var result = previous_seven_month_days(year, month, day);

    Test.assertEqual(result.size(), expected.size());

    for (var i = 0; i < result.size(); ++i) {
    Test.assertEqual(result, expected);
    }
    }

    (:test)
    function test_last_seven_days(logger)
    {
    // end-of-month
    do_test_date(logger, 2017, 12, 1, [ 30, 29, 28, 27, 26, 25, 24 ]);
    do_test_date(logger, 2017, 12, 2, [ 1, 30, 29, 28, 27, 26, 25 ]);
    do_test_date(logger, 2017, 12, 3, [ 2, 1, 30, 29, 28, 27, 26 ]);
    do_test_date(logger, 2017, 12, 4, [ 3, 2, 1, 30, 29, 28, 27 ]);
    do_test_date(logger, 2017, 12, 5, [ 4, 3, 2, 1, 30, 29, 28 ]);

    // end-of-year
    do_test_date(logger, 2017, 1, 1, [ 31, 30, 29, 28, 27, 26, 25 ]);
    do_test_date(logger, 2017, 1, 2, [ 1, 31, 30, 29, 28, 27, 26 ]);
    do_test_date(logger, 2017, 1, 3, [ 2, 1, 31, 30, 29, 28, 27 ]);
    do_test_date(logger, 2017, 1, 4, [ 3, 2, 1, 31, 30, 29, 28 ]);
    do_test_date(logger, 2017, 1, 5, [ 4, 3, 2, 1, 31, 30, 29 ]);

    // non-leap year
    do_test_date(logger, 2017, 3, 1, [ 28, 27, 26, 25, 24, 23, 22 ]);
    do_test_date(logger, 2017, 3, 2, [ 1, 28, 27, 26, 25, 24, 23 ]);
    do_test_date(logger, 2017, 3, 3, [ 2, 1, 28, 27, 26, 25, 24 ]);

    // leap year
    do_test_date(logger, 2016, 3, 1, [ 29, 28, 27, 26, 25, 24, 23 ]);
    do_test_date(logger, 2016, 3, 2, [ 1, 29, 28, 27, 26, 25, 24 ]);
    do_test_date(logger, 2016, 3, 3, [ 2, 1, 29, 28, 27, 26, 25 ]);


    return true;
    }

    }
    [/code]

  • except I'd cut out the Gregorian.info() calls from this function and move them to the caller


    OK, the test is a really good argument, but it don't seems to be more efficient, since I need the info once in short and once in medium format. Furthermore I call it just once during onLoad. To give you some more context: what I am trying to do is, storing the resting heart rate (which is measured by my watchface) of the last 7 days in the object store to calculate the average. I store two values each day: one value the resting HR itself, the other one the measured day. On load I will do a check, which values are valid (assuming that the watchface is maybe not active on some days due to changing it by user I will have some valid measurements and some not). Maybe there is a better software pattern to do this.
  • I need the info once in short and once in medium format.

    The code you posted only needs the day of month, month, and year. Nothing seems to needs a Gregorian.Info in FORMAT_MEDIUM. Your code..

    var today = Time.Gregorian.info(Time.now(), Time.FORMAT_MEDIUM).day;
    var month = Time.Gregorian.info(Time.now(), Time.FORMAT_SHORT).month;


    The first call gets the weekday and month fields in abbreviated format, but you don't extract either of those fields. The day field should still be an integer representing the day of month. The second call gets the weekday and month fields in integer format. If this is right, these two can be combined into one Gregorian.info() call using FORMAT_SHORT.

    But yes, making one or two calls to Gregorian.info() isn't going to be all that bad for a single invocation of your application. If someone was to borrow the code you've posted and call it from onUpdate() (or onPartialUpdate()) it would be more significant.

    Travis


  • Just to follow up on the issue of subtracting a Time.Duration from a Time.Moment.. A fix has been made, but it will not be able to roll out for a little while due to compatibility concerns.

    Travis