迭代日期数组的函数会产生意想不到的结果

Function to iterate over an array of dates produces unexpected results

我有一个从我的 iOS 应用调用的 CloudCode 函数。该函数应该创建一个 "checkin" 记录和 return 一个字符串来表示最近 30 天的签到和错过的天数。

奇怪的是,有时我能得到预期的结果,有时却得不到。这让我觉得我可能正在使用时区存在一些问题 - 因为这可能会导致不同的 "days in the past" 集,具体取决于我 运行 这个功能的时间和我检查的一天中的什么时间-在过去。但我很困惑,可以在这里使用一些帮助。

让我感到困惑的是,我没有看到我的所有 console.log() 结果都出现在解析日志中。这正常吗??例如,在 for 循环中,我可以取消注释 console.log 条目并调用该函数,但我不会看到列出的所有过去的日子 - 但它们包含在最终数组和文本字符串中。

这是我的完整功能。感谢任何帮助和建议。

/* Function for recording a daily check in
 *
 * Calculates the number of days missed and updates the string used to display the check-in pattern.
 * If no days missed then we increment the current count
 *
 * Input:
 * "promiseId" : objectID,
 * "timeZoneDifference" : String +07:00
 *
 * Output:
 * JSON String  eg. {"count":6,"string":"000000000000001111101010111111"}
 *
 */
Parse.Cloud.define("dailyCheckIn", function(request, response) {
    var promiseId = request.params.promiseId;
    var timeZoneDifference = request.params.timeZoneDifference;    
    var currentUser = Parse.User.current();

    if (currentUser === undefined) {
        response.error("You must be logged in.");
    }

    if (timeZoneDifference === undefined || timeZoneDifference === "") {
        //console.log("timeZoneDifference missing. Set to -07:00");
        timeZoneDifference = '' + '-07:00'; // PacificTime as string
    }

    var moment = require('cloud/libs/moment.js');

    // Query for the Promise
    var Promise = Parse.Object.extend("Promise");
    var queryforPromise = new Parse.Query(Promise);

    queryforPromise.get(promiseId, {
        success: function(promis) {

            // Initialize
            var dinarowString = "";
            var dinarowCount = 0;

            // Last Check In date from database (UTC)
            var lastCheckInUTC = promis.get("lastCheckIn");
            if (lastCheckInUTC === undefined) {
                lastCheckInUTC = new Date(2015, 1, 1);
            }

            // Use moment() to convert lastCheckInUTC to local timezone
            var lastCheckInLocalized = moment(lastCheckInUTC.toString()).utcOffset(timeZoneDifference);
                //console.log('lastCheckIn: ' + lastCheckInUTC.toString());
                //console.log('lastCheckInLocalized: ' + lastCheckInLocalized.format());

            // Use moment() to get "now" in UTC timezone
            var today = moment().utc(); // new Date(); 
                //console.log('today: ' + today.format());

            // Use moment() to get "now" in local timezone
            var todayLocalized = today.utcOffset(timeZoneDifference);
                //console.log('todayLocalized: ' + todayLocalized.format());

            // 30 days in the past
            var thirtydaysago = moment().utc().subtract(30, 'days');
                //console.log("thirtydaysago = " + thirtydaysago.format());

            // 30 days in the past in local timezone
            var thirtydaysagoLocalized = thirtydaysago.utcOffset(timeZoneDifference);
                //console.log('thirtydaysagoLocalized: ' + thirtydaysagoLocalized.format());

            // Calculate the number of days since last time user checked in
            var dayssincelastcheckin = todayLocalized.diff(lastCheckInLocalized, 'days');
                //console.log("Last check-in was " + dayssincelastcheckin + " days ago");

            // Function takes an array of Parse.Objects of type Checkin
            // itterate over the array to get a an array of days in the past as numnber
            // generate a string of 1 and 0 for the past 30 days where 1 is a day user checked in
            function dinarowStringFromCheckins(checkins) {
                var days_array = [];
                var dinarowstring = "";

                // Create an array entry for every day that we checked in (daysago)
                for (var i = 0; i < checkins.length; i++) {
                    var checkinDaylocalized = moment(checkins[i].get("checkInDate")).utcOffset(timeZoneDifference);
                    var daysago = todayLocalized.diff(checkinDaylocalized, 'days');
                    // console.log("daysago = " + daysago);
                    days_array.push(daysago);
                }
                console.log("days_array = " + days_array);

                // Build the string with 30 day of hits "1" and misses "0" with today on the right
                for (var c = 29; c >= 0; c--) {
                    if (days_array.indexOf(c) != -1) {
                        //console.log("days ago (c) = " + c + "-> match found");
                        dinarowstring += "1";
                    } else {
                        dinarowstring += "0";
                    }
                }
                return dinarowstring;
            }

            // Define ACL for new Checkin object
            var checkinACL = new Parse.ACL();
            checkinACL.setPublicReadAccess(false);
            checkinACL.setReadAccess(currentUser, true);
            checkinACL.setWriteAccess(currentUser, true);

            // Create a new entry in the Checkin table
            var Checkin = Parse.Object.extend("Checkin");
            var checkin = new Checkin();
            checkin.set("User", currentUser);
            checkin.set("refPromise", promis);
            checkin.set("checkInDate", today.toDate());
            checkin.setACL(checkinACL);
            checkin.save().then(function() {
                // Query Checkins
                var Checkin = Parse.Object.extend("Checkin");
                var queryforCheckin = new Parse.Query(Checkin);
                queryforCheckin.equalTo("refPromise", promis);
                queryforCheckin.greaterThanOrEqualTo("checkInDate", thirtydaysago.toDate());
                queryforCheckin.descending("checkInDate");
                queryforCheckin.find().then(function(results) {
                    var dinarowString = "000000000000000000000000000000";
                    var dinarowCount = 0;
                    if (results.length > 0) {
                        dinarowString = dinarowStringFromCheckins(results);
                        dinarowIndex = dinarowString.lastIndexOf("0");
                        if (dinarowIndex === -1) { // Checked in every day in the month!
                            // TODO
                            // If the user has checked in every day this month then we need to calculate the 
                            // correct streak count in a different way 
                            dinarowString = "111111111111111111111111111111";
                            dinarowCount = 999;
                        } else {
                            dinarowCount = 29 - dinarowIndex;
                        }
                    }
                    // Update the promise with new value and save
                    promis.set("dinarowString", dinarowString);
                    promis.set("dinarowCount", dinarowCount);
                    promis.set("lastCheckIn", today.toDate());
                    promis.save().then(function() {
                        response.success(JSON.stringify({
                            count: dinarowCount,
                            string: dinarowString
                        }));
                    });
                }, function(reason) {
                    console.log("Checkin query unsuccessful:" + reason.code + " " + reason.message);
                    response.error("Something went wrong");
                });

            }); // save.then
        },
        error: function(object, error) {
            console.error("dailyCheckIn failed: " + error);
            response.error("Unable to check-in. Try again later.");
        }
    });
});

你的问题太多了,无法充分回答,但我会很好,至少指出一些你应该研究的错误:

  1. 您根据固定偏移量进行输入,但随后您进行的操作会减去 30 天。您完全有可能跨越夏令时边界,在这种情况下,偏移量将发生变化。

    请参阅 timezone tag wiki. In moment, you can use time zones names like "America/Los_Angeles" with the moment-timezone 附加组件中的 "Time Zone != Offset"。

    从你的例子来看,我什至不确定时区对你的用例是否重要。

  2. 您不应该为了再次解析而将 Date 转换为字符串。 Moment 可以接受 Date 对象,前提是 Date 对象已正确创建。

    moment(lastCheckInUTC.toString()).utcOffset(timeZoneDifference)
    

    变成

    moment(lastCheckInUTC).utcOffset(timeZoneDifference)
    

    由于 Date.toString() returns 特定于语言环境、特定于实现的格式,您还会在调试控制台中看到一条警告。

至于其余部分,我们无法 运行 您的程序并重现结果,因此我们无能为力。您需要先调试自己的程序,然后尝试在 Minimal, Complete, and Verifiable example 中重现您的错误。很有可能,您会一路解决自己的问题。如果没有,那么您将有更好的状态与我们分享。

我正在回答我自己的问题,因为我找到了解决方案。

我有两个问题。第一个是 "why do I get unexpected (incorrect) results",我怀疑它与我使用时区的方式有关。我每天都会看到不同的结果,具体取决于我签到的时间。

问题实际上与 moment().diff() 的工作方式有关。 Diff 没有按照我预期的方式计算 "days"。如果我将今天凌晨 2 点与昨天晚上 11 点进行比较,diff 将显示 0 天,因为它小于 24 小时 diff。如果我将周四凌晨 1 点与前周一晚上 8 点进行比较,diff 将报告 2 天 - 而不是我预期的 3 天。这是一个精度问题。 Diff 认为 2.4 天是 2 天前。但是已经超过2了所以是3天前了

我们发现最简单的解决方案是在午夜而不是数据库中记录的实际时间比较两个日期。这会在几天内产生正确的结果。其余代码工作正常。

            //Find start time of today's day
            var todayLocalizedStart = todayLocalized.startOf('day');

            for (var i = 0; i < checkins.length; i++) {
                var checkinDaylocalized = moment(checkins[i].get("checkInDate")).utcOffset(timeZoneDifference);

                //Find start time of checkIn day
                var checkinDaylocalizedStart = checkinDaylocalized.startOf('day');

                //Find number of days
                var daysago = todayLocalizedStart.diff(checkinDaylocalizedStart, 'days');
                // console.log("daysago = " + daysago);

                days_array.push(daysago);
            }

我的第二个问题是 "is it normal to not see every console.log at runtime"。我与其他 Parse.com 用户交谈过,他们报告说 Parse 在日志记录中不一致。我花了很多时间调试 "problems" 只是 Parse 没有正确记录。

感谢所有为答案做出贡献的人。

我确实进行了另一项更改 - 但这不是错误。我将过去 30 天的查询限制替换为简单的“30”。只是简单了一点,少了一次计算。