How to Create Negative Firebase Timestamp in Swift

How to create negative Firebase timestamp in swift

First, see the linked answer in the comments. That answer relies on the client to generate a timestamp that's made negative and written to Firebase.

If you want to have Firebase generate a timestamp, that can be done as well with this little snappy Firebase structure and piece of code.

First, let's take a look at the structure

root
parent_node
-Y8j8a8jsjd0adas
time_stamp: -1492030228007
timestamp: 1492030228007

Next, some code to create and work with that structure:

Define a var we can use within our class that references the Firebase time stamp

let kFirebaseServerValueTimestamp = [".sv":"timestamp"]

and a function that adds an observer to the timestamp node:

func attachObserver() {

let timestampRef = self.ref.child("timestamp")
let parentNodeRef = self.ref.child("parent_node")
var count = 0

timestampRef.observe(.value, with: { snapshot in

if snapshot.exists() {
count += 1
if count > 1 {
let ts = snapshot.value as! Int
let neg_ts = -ts
let childNodeRef = parentNodeRef.childByAutoId()
let childRef = childNodeRef.child("time_stamp")
childRef.setValue(neg_ts)
count = 0
}
}
})

And a function that writes out a timestamp, therefore causing the observer to fire which creates child nodes within the parent_node based on the Firebase time stamp

func doTimestamp() {
let timestampRef = self.ref.child("timestamp")
timestampRef.setValue(kFirebaseServerValueTimestamp)
}

Here's the rundown.

In the attachObserver function, we attach an observer to the timestamp node - that node may or may not exist but if it doesn't it will be created - read on. The code in the closure is called any time an event occurs in the timestamp node.

When the doTimestamp function is called, it creates and writes a timestamp to the timestamp node, which then fires the observer we attached in attachObserver.

The code in the observe closure does the following:

Make sure the snapshot contains something, and if it does, increment a counter (more on that in a bit). If the counter is greater than 1 get the timestamp as an integer from the snapshot. Then, create it's negative and write it back out to Firebase as a child of parent_node.

How this would apply would be anytime you want to timestamp a child node with a Firebase generated timestamp but negative value for reverse loading/sorting - which speaks to the OP question.

The gotcha here is that when this happens

    timestampRef.setValue(kFirebaseServerValueTimestamp)

It actually writes twice to the node, which would cause the code in the closer to be called twice.

Maybe a Firebaser can explain that, but we need to ignore the first event and capture the second, which is the actual timestamp.

So the first event will cause the observer closer to fire, making count = 1, which will be ignored due to the if statement.

Then the second event fires, which contains the actual timestamp, and that's what we use to make negative and write out to Firebase.

Hope this helps the OP and the commenters.

Using the current timestamp to query all post before that time

Given the following structure

messages
msg_0
msg: "oldest message"
timestamp: -1
msg_1
msg: "hello"
timestamp: -4
msg_2
msg: "hello"
timestamp: -2
msg_3
msg: "newest message"
timestamp: -5
msg_4
msg: "hello"
timestamp: -3

and the following code

let messagesRef = self.ref.child("messages")
let queryRef = messagesRef.queryOrdered(byChild: "timestamp")
queryRef.observe(.childAdded, with: { snapshot in
let key = snapshot.key
print(key)
})

the output will be

msg_3 <-newest message
msg_1
msg_4
msg_2
msg_0 <-oldest message

As you can see, msg_3 is the newest message and appears 'at the top' i.e. it's the first snapshot the query receives. If you are populating an array to be used as a datasource, this will be added at index 0, then msg_1 would be at index 1 etc.

If the user scrolls and you want to load the next 5, it would be timestamps 6 to 10, with 10 being the newest.

Now suppose you want to load in the oldest two messages. Here's the query

let queryRef = messagesRef.queryOrdered(byChild: "timestamp")
.queryStarting(atValue: -3, childKey: "timestamp")
.queryEnding(atValue: -1, childKey: "timestamp")

the result

msg_2
msg_0

then, we want to load in the next oldest two

let queryRef = messagesRef.queryOrdered(byChild: "timestamp")
.queryStarting(atValue: -5, childKey: "timestamp")
.queryEnding(atValue: -3, childKey: "timestamp")

which gives us

msg_1
msg_4

Obviously I substitutes -5, -4 etc for an actual timestamp but it would work the same way.

How to get the timestamp from servervalue?

You can't use the ServerValue.TIMESTAMP to get the Firebase's server time.

It maps to the Firestore timestamp value for the server time when you write it to a document.

If you want the server time, there are two ways.

  1. Write the ServerValue.TIMESTAMP to a temporary doc in Firestore and read it from there.

  2. Use a Google Cloud function to get an instance of Date() object as a string in response.end() method. Create an HTTP endpoint like this. You can then use an AJAX request to get the result.

const app = (req, res) => {
// this will return the Firebase server time
res.send(new Date());
};

iOS simulator timestamp or Date() issue?

I think best practice here is that if your app is timestamp sensitive, to use a fixed and stable timestamp source which would be using Firebase Timestamp.

Locally generated dates or timestamps can be unstable and unpredictable; if the device hasn't updated to local time wherever it is, or they have that feature was turned off or maybe they just changed the time for some other reason. That can lead to inaccuracies in your data and sorting sequence.

The Real Time Database Timestamp and Timestamp has a different implementation than the Cloud Firestore one but there are a number of posts and examples here for implementing a server based timestamp.

Does Firebase always guarantee added events in order?

The order in which the data for a query is returns is consistent, and determined by the server. So all clients are guaranteed to get the results in the same order.

For new data that is sent to the database after the listeners are attached, all remote clients will receive it in the same order. The local client will see events for it's write operation right away though, before the data even reaches the database server.

In figure 2, it is actually quite simple: since each node has a unique timestamp, and they will be returned in the order of that timestamp. But even if they'd have the same timestamp, they'd be returned in the same order (timestamp first, then key) for each client.

Once a 13 digit timestamp is saved in firebase, how can it be fetched and converted into a date in Xcode?

Since you listen to UserS->caption then snapshot.value is an Int . so you need

databaseRef.child("UserS").child(uid).child("caption").observeSingleEvent(of: .value, with: { (snapshot) in
guard let caption = snapshot.value as? Int else { return }
print(caption)
let date = Date(timeIntervalSince1970: TimeInterval(caption)/1000.0)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd hh:mm:ss.SSSSxxx"
let res = dateFormatter.string(from: date)
print(res)
}

Edit:

if Calendar.current.dateComponents([.day], from:date, to: Date()).day! > 0 {
/// at least 1 day passed
}

In Firebase, how can I query the most recent 10 child nodes?

The answer is that you need to use a bit of reverse logic, and also store a timestamp key:value pair within each node as a negative value. I omitted the user_id: 1 to keep the answer cleaner.

Here's the Firebase structure

"test" : {
"-KFUR91fso4dEKnm3RIF" : {
"timestamp" : -1.46081635550362E12
},
"-KFUR9YH5QSCTRWEzZLr" : {
"timestamp" : -1.460816357590991E12
},
"-KFURA4H60DbQ1MbrFC1" : {
"timestamp" : -1.460816359767055E12
},
"-KFURAh15i-sWD47RFka" : {
"timestamp" : -1.460816362311195E12
},
"-KFURBHuE7Z5ZvkY9mlS" : {
"timestamp" : -1.460816364735218E12
}
}

and here's how that's written out to Firebase; I just used a IBAction for a button to write out a few nodes:

let testRef = self.myRootRef.childByAppendingPath("test")

let keyRef = testRef.childByAutoId()

let nodeRef = keyRef.childByAppendingPath("timestamp")

let t1 = Timestamp

nodeRef.setValue( 0 - t1) //note the negative value

and the code to read it in

    let ref = self.myRootRef.childByAppendingPath("test")
ref.queryOrderedByChild("timestamp").queryLimitedToFirst(3).observeEventType(.ChildAdded, withBlock: { snapshot in
print("The key: \(snapshot.key)") //the key
})

and I declared a little function to return the current Timestamp

var Timestamp: NSTimeInterval {
return NSDate().timeIntervalSince1970 * 1000
}

and the output

The key: -KFURBHuE7Z5ZvkY9mlS
The key: -KFURAh15i-sWD47RFka
The key: -KFURA4H60DbQ1MbrFC1

As you can see, they are in reverse order.

Things to note:

  1. Writing out your timestamp as negative values
  2. When reading in use .queryLimitedToFirst instead of last.

On that note, you can also just read the data as usual and add it to an Array then then sort the array descending. That puts more effort on the client and if you have 10,000 nodes may not be a good solution.



Related Topics



Leave a reply



Submit