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

Function to iterate over an array of dates produces unexpected results

本文关键字:意想不到 结果 迭代 日期 数组 函数      更新时间:2023-09-26

我有一个从我的iOS应用程序调用的CloudCode函数。该函数应该创建一个"签入"记录,并返回一个字符串来表示最近30天的签入和错过的天数。

奇怪的是,有时我得到预期的结果,有时却没有。这让我认为我使用的时区可能有一些问题——因为这可能会导致一组不同的"过去的日子",这取决于我运行这个函数的时间和我过去签到的时间。但是我很困惑,需要一些帮助。

让我感到困惑的是,我没有看到所有的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天。你完全有可能会跨越夏令时边界,在这种情况下,偏移量将会改变。

    请参阅时区标签wiki中的"Time Zone != Offset"。在moment,您可以使用像"America/Los_Angeles"这样的时区名称和moment-timezone附加组件。

    从你的例子中,我甚至不确定时区是否对你的用例很重要。

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

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

    moment(lastCheckInUTC).utcOffset(timeZoneDifference)
    

    由于Date.toString()返回特定于语言环境、特定于实现的格式,因此您还将在调试控制台中看到一个警告。

至于其余的,我们不能运行你的程序并重现结果,所以我们无能为力。您需要从调试自己的程序开始,然后尝试在最小化、完整和可验证的示例中重现您的错误。很有可能,你会在这个过程中解决自己的问题。如果没有,那么你将有更好的状态与我们分享。

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

我有两个问题。第一个问题是"为什么我会得到意想不到的(不正确的)结果",我怀疑这与我使用时区的方式有关。我每天都会看到不同的结果,这取决于我签到的时间。

问题实际上与moment().diff()的工作方式有关。Diff并没有像我期望的那样计算"天数"。如果我比较今天凌晨2点和昨天晚上11点,diff会说0天,因为它不到24小时。如果我比较周四凌晨1点和前周一晚上8点,diff会报告2天,而不是我预期的3天。这是一个精度问题。Diff认为2.4天是2天前。

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

            //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);
            }

我的第二个问题是"在运行时看不到每个console.log是否正常"。我与其他Parse.com用户交谈过,他们报告说Parse在日志记录方面不一致。我花了很多时间调试"问题",只是解析没有正确记录日志。

感谢每个为这个答案做出贡献的人。

我确实做了另一个更改-但它不是一个错误。我将查询限制从过去的30天改为简单的"30"。它只是简单了一点,少了一个计算。