Fetching timestamp from firestore Cloud Functions nodejs - node.js

I am an interface where I have declared lastLogin as Date.
LastLogin looks like this:
export interface User {
lastLogin: Date,
}
I am fetching this timestamp in my cloud function and I want it as a timestamp so that I can subtract it from the current Timestamp.
const mentor: User = mentorDoc.data() as User;
const login = mentor['lastLogin'];
const lastOpen = mentor['lastLogin'].valueOf();
const currentTime = new Date().getTime();
const diffMs = (currentTime - lastOpen);
const diffHrs = Math.floor((diffMs % 86400000) / 3600000); // hours
Last open shows like this :
063740174400.000000000
And when I use to like this :
const lastOpen = new Date (mentor['lastLogin'].valueOf());
Output:
undefined

Please change Date to firestore Timestamp both in the interface declaration as well as the collection field that holds the date in the firestore console
export interface User {
lastLogin: firebase.firestore.Timestamp,
}
You can now retrieve the date and also update it, this time simply by passing a new Date object.
// Read currently saved Timestamp
const ref = db.collection<User>('users').doc(`${uid}`).get().toPromise()
const saved_date = ref?.data().lastLogin
const current_date = new Date()
// if you just want the Date difference in ms after retrieving the timestamp from firestore.
const diff = current_date.getMilliseconds() - (saved_date.toDate()).getMilliseconds() // Timestamp -> Date (ms)
// Update Timestamp using a Javascript Date object
db.collection('..').doc('..').set({ lastLogin: current_date }, {merge:true})
// set creates the field while update works only if field already exists

Related

Firebase Firestore cloud functions: filtering documents by date conditions in node.js

I need to filter documents in Firestore by date within a cloud function using node.js. I'd like to return only those documents that have a timestamp (or date string) more than 30 days from the current date in a field named last_used. At present, not all documents have this field. One method I have thought about employing is writing a trigger function to give each document a last_used field and set the timestamp to 01/01/2022, run this once and then the scheduled function can update the field when required with the current date. The scheduled function currently selects a player document at random each day, moves the required fields into the current-live-player collection and updates the last_used field for the selected player document within available-players.
Context: I'm building a game in which a player document is selected at random each day from the available-players collection and placed into the current-live-player collection. current-live-player is made up of just this one document and updated each day at 00:00 London time. I only want Firestore to select a player document from available-players that meets my aforementioned condition: the date within last_used is more than 30 days prior to the current date.
This is what I have so far:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const { firestore } = require("firebase-admin");
admin.initializeApp();
const db = admin.firestore();
// Potential trigger function to give all player objects a last_used field
exports.addLastUsedToAll = functions.https.onRequest((request, response) => {
// Logic to add a last_used field with a date of 01/01/2022
});
// Select a player function that runs every day at 00:00 London time
exports.pickCurrentLivePlayer = functions.pubsub.schedule("0 0 * * *")
.timeZone("Europe/London")
.onRun(async () => {
// select a random player from available players (uses the auto-generated IDs)
// ifelse catches rare cases that could cause an error
const availablePlayers = db.collection("available-players");
const key = availablePlayers.doc().id;
availablePlayers.where(admin.firestore.FieldPath.documentId(), '>=', key).limit(1).get()
.then(snapshot => {
if(snapshot.size > 0) {
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
console.log('snapshot.size > 0 ', doc.id, '=>', doc.data());
// replace live player
const newPlayerData = doc.data();
const app_first_name = newPlayerData.app_first_name;
const uid = newPlayerData.uid;
db.collection("live-player").doc("current-live-player").set({app_first_name: app_first_name, uid: uid});
// set selected player's last_used value to current time
db.collection("available-players").doc(uid).update({
last_used: admin.firestore.Timestamp.now()
})
});
}
else {
const player = availablePlayers.where(admin.firestore.FieldPath.documentId(), '<', key).limit(1).get()
.then(snapshot => {
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
console.log('snapshot.size > 0 ', doc.id, '=>', doc.data().name);
// replace live player
const newPlayerData = doc.data();
const app_first_name = newPlayerData.app_first_name;
const uid = newPlayerData.uid;
db.collection("live-player").doc("current-live-player").set({app_first_name: app_first_name, uid: uid});
// set selected player's last_used value to current time
db.collection("available-players").doc(uid).update({
last_used: admin.firestore.Timestamp.now()
})
});
})
.catch(err => {
console.log('Error getting documents', err);
});
}
})
.catch(err => {
console.log('Error getting documents', err);
});
return null;
});
I hope that all makes sense, any help would be greatly appreciated. I know that it can be tricky to convert timestamps and then filter by date in javascript, but I'd like to use the Firestore timestamp to ensure that the functions are always running off the server time.
Edit:
In response to Frank van Puffelen's answer, the timestamp calculations are now working and each document in available players now has a last_used field populated with a timestamp. I now need to query available_players to show those documents in which the timestamp is more than 30 days ago. The issue now is that I cannot query the collection before running my random selection. The order should be: 1) calculate today's date minus 30 days; 2) query available_players for all documents with a timestamp in the last_used field more than 30 days in the past; 3) generate a key from this filtered selection; 4) query the filtered selection to give me one random document and process the rest of the update logic etc. It falls down where the filtered data is now a query snapshot rather than a collection reference and the code I have no longer runs (at least I think that's the issue!). The code worked perfectly before attempting to add this 'filter by date' functionality. I've provided a minimal repro here:
// calculate time interval
const now = admin.firestore.Timestamp.now();
const intervalInMillis = 30 * 24 * 60 * 60 * 1000;
const cutoffTime = admin.firestore.Timestamp.fromMillis(now.toMillis() - intervalInMillis);
// get collectionReference and filter to those more than 30 days ago
const allPlayers = db.collection("available-players");
const availablePlayers = await allPlayers.where('last_used', '<=', cutoffTime).get();
// generate key
const key = availablePlayers.doc().id;
// select a random player from the filtered selection
availablePlayers.where(admin.firestore.FieldPath.documentId(), '>=', key).limit(1).get()
.then(snapshot => {
..etc etc
I somehow need to be able to query the data to filter it by date and then run the random selection chunk of code.
To get the players that were last used more than 30 days ago would be something like this:
const availablePlayers = db.collection("available-players");
const now = admin.firestore.Timestamp.now();
const intervalInMillis = 30 * 24 * 60 * 60 * 1000;
const cutoffTime = admin.firestore.Timestamp.fromMillis(now.toMillis() - intervalInMillis);
const query = availablePlayers.where(last_used, "<=", cutoffTime);

How to query by date on cloud function?

I'm trying to query some data that is between two dates, but nothing seems to work. These are my dates, I saw on a post that the division by 1000 should work , but it's not. Ive tried firebase.firestore but says firebase it's not defined but I don't know how to simply reference to firebase.firestore.Timestamp
let now = new Date()
let yesterday = Math.round((new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1).getTime())/1000)
now = Math.round(now.getTime()/1000)
This is my query attempt. Nothing works and it just returns my empty array because the query has no data to iterate through the forEach.
let snapshot = await db.collection('appointments')
.where('status', '==', 'Pending')
.where('startDate', '<=', now)
.where('startDate', '>', yesterday)
.get().then(docs => {
docs.forEach(snapshot => {
console.log(snapshot.id, '=>', snapshot.data());
console.log(snapshot.data()['doctor']);
doctor_mail.push(snapshot.id)
doctor_mail.push(snapshot.data())
});
return doctor_mail
}).
catch(err => {
return res.send(err)
});
console.log(doctor_mail)
res.send(snapshot)
The query is not returning anything because there is nothing to return. If you are querying for a string representing a date and the data is a timestamp in the Firestore, so in order to know that, it would be need to check a sample document to compare, since in the first part of your question you mentioned that you want to get a Firestore timestamp, you can do it with this code:
const timestamp = db.FieldValue.serverTimestamp();
//if you want it as a date object
const date = timestamp.toDate();
As per what you asked in the comments, for getting the value of Today and Yesterday in Timestamp you can do the following:
var todayTimestamp = timestamp.now();
var date = new Date().setDate(date.getDate() - 1);
var yesterdayTimestamp = timestamp.fromDate(date);
And convert them back to date so you can operate them if needed, you can check more details on the Timestamp in this Documentation
Okay, the thing here was that I was wrongly calling db as const db = firebase.firestore.
Instead I just had to go for:
const db = admin.firestore()

Mongoose MongoDB in Node.js - Saving datetime in local timezone

I am using Mongoose in NodeJS project. I have this schema:
let InventorySchema = new Schema({
name: String,
tradable: {
type: Date,
default: () => {
return new Date().getTime()
}
}
}, {
versionKey: false
});
I live in Prague (GMT+01:00). When I insert "line" into my document, tradable is set automatically (because of default) to datetime without GMT. For example time now is in my city 16:51 but into database its saved as 15:51
How to save correct datetime? NodeJS Date giving me correct datetime when it is called normally.
EDIT: Using Date.now not helping! Same output
Use getTimezoneOffset() It will provide you the offset :
var date = new Date(); //ex 2019-01-18T16:26:44.982Z
var offset = - date.getTimezoneOffset() / 60; //in your case 1
And you add the offset to your date.

MongoDB: How to get timestamp and ObjectID from newest document in collection?

I would like to get last created documents in collection and return their objectID and timestamps. For example if yesterday I created 10 documents I would like to return them with db.collection and then
const lastTimeStamp = will be the timestamp from the last created element
const lastTimeStampArray = will be array of timestamps from yesterdays records
const lastObjectId = will be ObjectID of last created document
const lastObjectIdsArray = array of last objectIds
I am using:
MongoDB's _id field has info about date stored in itself. The timestamp is contained in the first 4 bytes of a mongoDB id.
You can use ObjectId.getTimestamp() function to get time from _id of a document.
Sorting on an _id field that stores ObjectId values is roughly equivalent to sorting by creation time.
For you question:
// To get lastTimeStamp
db.collection.find().sort({ '_id': -1}).limit(1).forEach(
function(doc){
lastTimeStamp = doc._id.getTimestamp();
}
)
// to get lastObjectId
db.collection.find().sort({ '_id': -1}).limit(1).forEach(
function(doc){
lastObjectId = doc._id;
}
)
Now, to get all records inserted yesterday might be a bit of hard work. You need to extract all records inserted yesterday and from that you need to extract information you need.
// to get lastTimeStampArray and lastObjectIdsArray
var yesterdayStart = new Date();
yesterdayStart.setDate(yesterdayStart.getDate() - 1);
yesterdayStart.setHours(0,0,0,0);
var startId = Math.floor(yesterdayStart.getTime() / 1000).toString(16) + "0000000000000000";
var yesterdayEnd = new Date();
yesterdayEnd.setDate(yesterdayEnd.getDate() - 1);
yesterdayEnd.setHours(23,59,59,999);
var endId = Math.floor(yesterdayEnd.getTime() / 1000).toString(16) + "0000000000000000";
var lastTimeStampArray = [];
var lastObjectIdsArray = [];
db.collection("records")
.find( { _id: {
$gte: ObjectId(startId),
$lte: ObjectId(endId)
}
}
).forEach(
function(doc){
lastObjectIdsArray.push(doc._id);
lastTimeStampArray.push(doc._id.getTimestamp());
});
These are mongo shell commands you can write your node.js accordingly.
You can get last inserted record with timestamp using the following:
db.collection.find().sort({ '_id': -1 }).limit(1).forEach(
function(doc){
print("record:"+doc._id.getTimestamp());
})
_id is the Mongodb objectID

Mongoose date comparison

My application will allow user to create coupons.
Coupon will be valid in datefrom and dateto period.
The thing is that every coupon should be valid for selected days, not hours.
For example since Monday(2016-06-12) to Tuesday(2016-06-13), so two days.
How should I store dates on server side and then compare it using $gte clause in Mongoose?
Thank you :-)
{ "_id" : 1, "couponStartDate" : ISODate("2016-06-26T18:57:30.012Z") }
{ "_id" : 2, "couponStartDate" : ISODate("2016-06-26T18:57:35.012Z") }
var startDate = new Date(); // I am assuming this is gonna be provided
var validDate = startDate;
var parametricDayCount = 2;
validDate.setDate(validDate.getDate()+parametricDayCount);
CouponModel.find({couponStartDate: {$gte: startDate, $lte: validDate}}, function (err, docs) { ... });
You can store expiration time as UNIX timestamp. In your Mongoose model you can use expiration : { type: Number, required: true}
If you have user interface for creating coupons then you can configure your date picker to send time in UNIX timestamp.
Or If you are getting Date string then you can use var timestamp = new Date('Your_Date_String');
And for calculation of Days you can use Moment JS. Using this you can calculate start of the date using .startOf(); and end of date using .endOf();
Timestamp return from Moment JS can be used for Mongoose query like $gte : some_timestamp and $lte : some_timestamp
If you want to validate the coupon before it is persisted, you can create a max / min value for the date field:
See this sample from official mongoose documentation on DATE validation:
var s = new Schema({ dateto: { type: Date, max: Date('2014-01-01') })
var M = db.model('M', s)
var m = new M({ dateto: Date('2014-12-08') })
m.save(function (err) {
console.error(err) // validator error
m.dateto = Date('2013-12-31');
m.save() // success
})
Hint: use snake_case or camelCase for field names

Resources