我正在使用Firestore编写一个Web应用程序,该应用程序需要能够显示"今天的热门帖子"在考虑不同时区的用户时,我无法正确查询。
日期作为UTC 0存储在数据库中,然后通过Moment.js调整到客户端当前用户的UTC偏移量。这可以正常工作。
添加新帖子时,我使用firebase.firestore.FieldValue.serverTimestamp()
将当前服务器时间戳存储在名为timestamp
的字段中,如下所示:
const collectionRef = db.collection('posts');
collectionRef.add({
name: "Test Post",
content: "Blah blah blah",
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
likeCount: 0
});
然后在服务器上,我有一个在创建时运行的云功能,并将另一个字段添加到名为datestamp
的文档中,这是UTC 0时间戳,但进行了调整,以便时间是一天的开始。该函数如下所示:
exports.updatePostDate = functions.firestore
.document('posts/{postID}')
.onCreate((event) => {
const db = admin.firestore();
const postRef = db.doc('post/'+event.params.postID);
const postData = event.data.data();
const startOfDay = moment(postData.timestamp).startOf('day').toDate();
return postRef.update({
datestamp: startOfDay
});
});
存储时间始终是一天开始的时间戳使我能够编写这样的查询,以查找所有帖子并在特定日期按人气排序:
const startOfDayUTC = moment.utc().startOf('day').toDate();
const postQuery = db.collection('posts')
.orderBy('likeCount', 'desc')
.orderBy('timestamp', 'desc')
.where('datestamp', '==', startOfDayUTC)
.limit(25);
问题是,根据用户的UTC偏移量,这可以在解析帖子的timestamp
字段时显示具有两个不同日期的帖子。因此,即使查询正确地获取了datestamp
所说的所有帖子,2018-01-30T00:00:00Z,timestamp
的日期在解析后可能也不一样。以下是两个帖子的示例:
Post 2:
likeCount: 1
timestamp (UTC 0): 2018-01-30T06:41:58Z
timestamp (parsed to UTC-8): 2018-01-29T22:41:58-08:00
datestamp (UTC 0): 2018-01-30T00:00:00Z
Post 1:
likeCount: 0
timestamp (UTC 0): 2018-01-30T10:44:35Z
timestamp (parsed to UTC-8): 2018-01-30T02:44:35-08:00
datestamp (UTC 0): 2018-01-30T00:00:00Z
因此,您可以看到,在将datestamp
调整为本地UTC后,帖子具有相同的timestamp
,timestamp
字段最终会在两个不同的日期结束。< / p>
如果有人对此有解决方案,我将非常感激。
答案 0 :(得分:3)
我认为在这种情况下最好避免使用函数,因为您现在可以执行复合查询。你可以简单地使用
query.where(date > lastMidnight).where(data < now).get().then(...)
可以说限制只属于一天的数据,并尝试将所有时间变量保存在UTC 0中,只需找到客户端的起点和当前时间,然后将它们转换为UTC0。
//get local time from midnight to now (local)
const now = new Date();
const lastMidnight = now.setHours(0,0,0,0);
//then convert those to UTC0 to pass on in your query to firestore
const lastMidNightUTC = new Date(lastMidnight + now.getTimezoneOffset() * 60000).toString();
const nowInUTC = new Date(now + now.getTimezoneOffset() * 60000).toString();
你可以获取你的数据(记住你需要制作一个索引或只需运行一次查询,firebase SDK将生成一个链接以在开发工具中创建索引 - > gt;控制台,为你服务)
query.where(date > lastMidNightUTC).where(data < now).get().then(...)
答案 1 :(得分:0)
我提出了一个我真的不满意的解决方案......但它确实有效!
问题基本上是一个帖子可以在多个日期,具体取决于用户的位置。因为在这种情况下,我们还希望按timestamp
以外的字段进行排序,我们无法使用范围查询来选择指定日期的帖子,因为您的第一个.orderBy
必须在该字段上'(see Firestore docs)使用范围查询。
我的解决方案是将本地化的日期戳映射到相应的UTC偏移量。该对象包含每个UTC偏移量作为键,以及该偏移量时间内的帖子的日期戳。
示例帖子如下所示:
posts/{somepostid}
{
name: "Test Post",
content: "Blah blah blah",
timestamp: Mon Jan 29 2018 21:37:21 GMT-0800 (PST),
likeCount: 0,
utcDatemap: {
0: "2018-01-30,
...
-480: "2018-01-29",
...
}
}
字段utcDatemap
是我们现在在查询中使用的字段:
const now = moment();
const datestamp = now.format("YYYY-MM-DD");
const utcOffset = now.utcOffset();
const utcDatemapField = 'utcDatemap.'+utcOffset;
const postQuery = db.collection('posts')
.orderBy('likeCount', 'desc')
.orderBy('timestamp', 'desc')
.where(utcDatemapField, '==', datestamp)
.limit(25);
现在帖子可以在两天内显示,具体取决于用户查询的位置。我们仍然可以将常规旧时间戳转换为用户在客户端上的本地时间。
这绝对不是一个理想的解决方案。对于上面的查询,我需要为utcDatemap中的每个键创建复合索引。我不确定复合索引的经验法则是什么,但我认为有39个索引用于简单的事情可能不是很好。
此外,我使用来自this post和utcDatemap对象的thomas-peter答案中的roughSizeOfObject
函数进行了检查,其中所有字符串的日期戳大约为780字节,并且不像0.78kb很多,但你需要注意你使用像Firestore这样的服务传输了多少数据(0.78kb对于某个日期来说很多)。