I'm trying to use Firebase timestamps in a Swift app. I'd like to store them in my Firebase, and use them as native NSDate objects in my app.
The docs say they are unix epoch time, so I've tried:
NSDate(timeIntervalSince1970:FirebaseServerValue.timestamp)
with no luck.
This:
FirebaseServerValue.timestamp
returns
0x00000001199298a0
according to the debugger. What is the best way to pass these timestamps around?
ServerValue.timestamp() works a little differently than setting normal data in Firebase. It does not actually provide a timestamp. Instead, it provides a value which tells the Firebase server to fill in that node with the time. By using this, your app's timestamps will all come from one source, Firebase, instead of whatever the user's device happens to say.
When you get the value back (from a observer), you'll get the time as milliseconds since the epoch. You'll need to convert it to seconds to create an NSDate. Here's a snippet of code:
let ref = Firebase(url: "<FIREBASE HERE>")
// Tell the server to set the current timestamp at this location.
ref.setValue(ServerValue.timestamp())
// Read the value at the given location. It will now have the time.
ref.observeEventType(.Value, withBlock: {
snap in
if let t = snap.value as? NSTimeInterval {
// Cast the value to an NSTimeInterval
// and divide by 1000 to get seconds.
println(NSDate(timeIntervalSince1970: t/1000))
}
})
You may find that you get two events raised with very close timestamps. This is because the SDK will take a best "guess" at the timestamp before it hears back from Firebase. Once it hears the actual value from Firebase, it will raise the Value event again.
For me in swift 5 use in another class:
import FirebaseFirestore
init?(document: QueryDocumentSnapshot) {
let data = document.data()
guard let stamp = data["timeStamp"] as? Timestamp else {
return nil
}
let date = stamp.dateValue()
}
This question is old, but I recently had the same problem so I'll provide an answer.
Here you can see how I am saving a timestamp to Firebase Database
let feed = ["userID": uid,
"pathToImage": url.absoluteString,
"likes": 0,
"author": Auth.auth().currentUser!.displayName!,
"postDescription": self.postText.text ?? "No Description",
"timestamp": [".sv": "timestamp"],
"postID": key] as [String: Any]
let postFeed = ["\(key)" : feed]
ref.child("posts").updateChildValues(postFeed)
The particularly relevant line of code is "timestamp": [".sv": "timestamp"],
This saves the timestamp as a double in your database. This is the time in milliseconds so you need to divide by 1000 in order to get the time in seconds. You can see a sample timestamp in this image.
To convert this double into a Date I wrote the following function:
func convertTimestamp(serverTimestamp: Double) -> String {
let x = serverTimestamp / 1000
let date = NSDate(timeIntervalSince1970: x)
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .medium
return formatter.string(from: date as Date)
}
This gives a timestamp that looks like this:
You will get the right time if you use:
let timestamp = FIRServerValue.timestamp()
let converted = NSDate(timeIntervalSince1970: timestamp / 1000)
let dateFormatter = NSDateFormatter()
dateFormatter.timeZone = NSTimeZone.localTimeZone()
dateFormatter.dateFormat = "hh:mm a"
let time = dateFormatter.stringFromDate(converted)
let serverTimeStamp = ServerValue.timestamp() as! [String:Any]
Store in Firebase something like [ktimeStamp:timestamp as AnyObject]
than after you convert in seconds using Firebase Server Time:
let timestampDate = NSDate(timeIntervalSince1970: Double(timestamp as! NSNumber)/1000)
Firestore has an API for this --> -(NSDate *)dateValue
For example, if you have saved(set) a new document with a field "createdAtDate"
NSDictionary *dataToBeSaved = #{
//Tell the server to save FIRTimestamps when the document is created
#"createdAtDate":[FIRFieldValue fieldValueForServerTimestamp],
#"lastModifiedDate":[FIRFieldValue fieldValueForServerTimestamp],
//Other fields
#"userName":#"Joe Blow"
}
[myFirReference setData:[dataToBeSaved]
options:[FIRSetOptions merge]
completion:^(NSError* error) {
}
You can get back this information either with a get query or via setting a listener. When you have the snapshot back, just access the dates you saved and convert to NSDate.
NSDate *date1 = [snapshot.data[#"createdAtDate"] dateValue];
NSDate *date2 = [snapshot.data[#"lastModifiedDate"] dateValue];
There will be a slight loss in precision, but as most people use dates for data synchronization or sorts, I can't think of a case where the loss of precision would be an issue.
You can create a new transformer for ObjectMapper,
import Foundation
import ObjectMapper
class FirebaseDateTransform: TransformType {
public typealias Object = Date
public typealias JSON = Double
open func transformFromJSON(_ value: Any?) -> Date? {
if let millisecondsSince1970 = value as? Double {
return Date(timeIntervalSince1970: millisecondsSince1970 / 1000.0)
}
return nil
}
open func transformToJSON(_ value: Date?) -> Double? {
if let date = value {
return Double(date.timeIntervalSince1970) * 1000.0
}
return nil
}
}
Gist
Here is some code, based on alicanbatur's answer, that allows a date to be a Double or a server timestamp, and yet still work within an object mapping layer such as ObjectMapper.
enum FirebaseDate {
case date(Date)
case serverTimestamp
var date: Date {
switch self {
case .date(let date):
return date
case .serverTimestamp:
return Date()
}
}
}
class FirebaseDateTransform: TransformType {
public typealias Object = FirebaseDate
public typealias JSON = Any
open func transformFromJSON(_ value: Any?) -> FirebaseDate? {
switch value {
case let millisecondsSince1970 as Double:
let date = Date(millisecondsSince1970: millisecondsSince1970)
return .date(date)
case is [AnyHashable: Any]?:
return .serverTimestamp
default:
return nil
}
}
open func transformToJSON(_ value: FirebaseDate?) -> Any? {
switch value {
case .date(let date)?:
return date.millisecondsSince1970
case .serverTimestamp?:
return ServerValue.timestamp()
default:
return nil
}
}
}
You can get a date approximation from Firebase. For example if you're trying to change a firebase user's creation date (a Timestamp) to a Date:
user.creationDate.dateValue()
Swift 4 and updated Firebase library variation of Katfang's answer:
let currentTimeStamp: TimeInterval?
let ref = Database.database().reference().child("serverTimestamp")
ref.setValue(ServerValue.timestamp())
ref.observe(.value, with: { snap in
if let t = snap.value as? TimeInterval {
print(t/1000)
currentTimeStamp = t/1000
}
})
Related
hi everyone I have this structure that I use to save data on firebase ... As you can see I have a var reservation_date: Date field
Firebase obviously saves a date with the time but I would need only the date without the time to be saved how can I get this?
This is how I save me
struct Reservations: Identifiable, Codable {
#DocumentID var id: String?
var reservation_date: Date
var user: String
#ExplicitNull var userID: String?
var reservation_id: String
#FirebaseFirestoreSwift.ServerTimestamp var createdAt: Timestamp?
enum CodingKeys: String, CodingKey {
case id
case reservation_date
case user
case userID
case reservation_id
case createdAt
}
static let sample = Reservations(reservation_date: Date(), user: "", userID: "", reservation_id: "")
}
// MARK: - Firebase
extension ReservationViewModel {
func addReservation() {
guard let newDate = Calendar.current.date(bySettingHour: hour, minute: minute, second: 0, of: selectedDate) else {
print("Non è stato possibile creare una data di prenotazione valida")
return }
let dBPath = db.collection("Prenotazioni")
.document(newDate.formatted(.dateTime.year()))
.collection(newDate.formatted(.dateTime.month(.wide).locale(Locale(identifier: "it"))))
.document(newDate.formatted(.dateTime.day(.twoDigits)))
.collection("Ore \(newDate.formatted(.dateTime.hour(.twoDigits(amPM: .omitted)).locale(Locale(identifier: "it"))) + ":" + newDate.formatted(.dateTime.minute(.twoDigits)))")
do {
let _ = try dBPath.addDocument(from: reservation)
}
catch {
print(error)
}
}
}
If you're only interested in the date value then I would recommend using a string that conforms to the ISO 8601 standard, which in this particular case would just be yyyy-mm-dd (ex: "2022-02-13"). Swift has a ready-made ISO-8601 formatter exactly for this.
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withFullDate]
// From String to Date
let reservationDay = "2022-03-25" // save this in your database
if let date = formatter.date(from: reservationDay) {
print(date) // Swift Date object
}
// From Date to String
let reservationDate = Date()
let day = formatter.string(from: reservationDate)
print(day) // String "2022-02-13"
Be mindful of timezones, however, because the default is GMT and you will want to use GMT throughout the conversions (and then render the local timezone value) or the local timezone throughout the conversions (just don't mix them as you convert). To always deal in the local timezone you can set its property on the formatter:
formatter.timeZone = .current
func getCurrentTimeIntervalSince1970()-> Int
{
return Int(NSDate().timeIntervalSince1970)
}
let lastLearned = getCurrentTimeIntervalSince1970()
let cardData = "\(cardId):\(newCardLevel):\(lastLearned)"
Note: Everything except lastLearned is unimportant. lastLearned is where I store current time in TimeIntervalSince1970.
let array = cardData.components(separatedBy: ":")
print("lastlearned : \(array[2])") //result: `2020-02-26 10:28:38.467046+0100`
Storing lastLearned in Realm:
RealmManager.shared.updateLevel(lastLearned: array[2])
Printing date from Realm:
let date = Date(timeIntervalSince1970: Double(card.last_learned)!)
print(date) //result: 2106-02-07 06:28:15 +0000 //HOW?
Update your getCurrentTimeIntervalSince1970 func to this
func getCurrentTimeIntervalSince1970() -> Int {
return Int(Date().timeIntervalSince1970)
}
let date = getCurrentTimeIntervalSince1970()
debugPrint(date)
debugPrint(Date(timeIntervalSince1970: Double(date)))
Result
1582711543
2020-02-26 10:05:43 +0000
Basically, I have an app on Firebase. The thing is, when Firebase sorts the data, instead of a chronological order, it muddles the data.
When I went online and search why, I found that it was because I was using the snapshot.value instead of snapshot.children.
However, I'm not completely sure how to change the code accordingly, could someone help?
Here is the code:
func retrieveChatLog() {
FIRDatabase.database().reference().child("chats").child(chatID).observe(.value, with: {(snapshot) in
let chats = snapshot.value as! [String : AnyObject]
self.messages.removeAll()
for (_, value) in chats {
if let sender = value["sender"], let message = value["message"], let senderID = value["senderUID"], let date = value["date"] {
let messageToShow = Message()
messageToShow.message = message as! String
messageToShow.sender = sender as! String
messageToShow.senderUID = senderID as! String
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
let curDate = formatter.date(from: date as! String)
messageToShow.date = curDate as! Date
if messageToShow.senderUID != "" {
self.messages.append(messageToShow)
}
}
}
self.tableView.reloadData()
})
FIRDatabase.database().reference().removeAllObservers()
}
The problem isn't because you use snapshot.value. You can use .value or .childAdded. The problem is, if you use .value, you don't want to cast the snapshots to a dictionary because dictionaries don't preserve order. Instead, you're going to want to cast to an array to preserve order. Here's one way you could resolve this:
FIRDatabase.database().reference().child("chats").child(chatID).observe(.value, with: {(snapshot) in
let chats = snapshot.children.allObjects as! [FIRDataSnapshot]
self.messages.removeAll()
for chat in chats {
if let value = chat.value as? [String: AnyObject] {
if let sender = value["sender"], let message = value["message"], let senderID = value["senderUID"], let date = value["date"] {
let messageToShow = Message()
messageToShow.message = message as! String
messageToShow.sender = sender as! String
messageToShow.senderUID = senderID as! String
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
let curDate = formatter.date(from: date as! String)
messageToShow.date = curDate as! Date
if messageToShow.senderUID != "" {
self.messages.append(messageToShow)
}
}
}
}
self.tableView.reloadData()
})
FIRDatabase.database().reference().removeAllObservers()
}
Change this line
FIRDatabase.database().reference().child("chats").child(chatID).observe(.value, with: {(snapshot) in...
To this
FIRDatabase.database().reference().child("chats").child(chatID).observe(.childAdded, with: {(snapshot) in
There is an accepted answer but I wanted to provide another solution that may be a bit tighter and more expandable.
It starts with a class to hold the messages. In this case I am keeping just the firebase key and timestamp but other vars could easily be added. You'll note the timestamp var that is read from Firebase in the same format: dd.mm.yy. This would be useful for sorting if needed. Also note that if you want to display a nicely formatted mm/dd/yyyy format, it's available though the formattedDate computed property.
The messagesArray is an array of MessageClass objects which can be used as a datasource for a tableView for example.
Finally, the loadMessages function to load in all of the messages. As mentioned in the other answer, casting the Snapshot to a dictionary loses ordering guarantee. However, if we iterate over the snapshot directly, the ordering stays intact.
class MessageClass {
var timestamp: String
var fbKey: String
var formattedDate: String {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
let d = formatter.date(from: timestamp)
let outputFormattter = DateFormatter()
outputFormattter.dateFormat = "MM/dd/yyyy"
let finalDate = outputFormattter.string(from: d!)
return finalDate
}
init(withSnap: DataSnapshot ) {
let snapDict = withSnap.value as! [String: AnyObject]
self.timestamp = snapDict["timestamp"] as! String
self.fbKey = withSnap.key
}
}
var messagesArray = [MessageClass]()
func doButton0Action() {
let messagesRef = self.ref.child("messages")
messagesRef.observe(.value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let m = MessageClass(withSnap: snap)
self.messagesArray.append(m)
}
for msg in self.messagesArray { //test code to print the array once loaded
print("key: \(msg.fbKey) date: \(msg.formattedDate)")
}
})
}
This code is very verbose and could be condensed considerably but it's very readable. (it needs guards and error checking as well).
I would strongly encourage you to store your timestamps in a
yyyymmddhhmmss
format in firebase. It lends itself to sorting / querying
Also, as a side-note, instead of relying on the date the node was created (by key) to keep your ordering, consider leveraging the timestamp when reading in the nodes using .order(by: timestamp). That will guarantee they are always in the correct order even if messages are changed around or the keys are modified.
I'm trying to order my data from Firebase so the most recent post is at the top (like Instagram), but I just can't get it to work properly. Should I be using a server timestamp? Is there a "createdAt" field?
func getPosts() {
POST_REF.observeEventType(.Value, withBlock: { snapshot in
guard let posts = snapshot.value as? [String : [String : String]] else {
print("No Posts Found")
return
}
Post.feed?.removeAll()
for (postID, post) in posts {
let newPost = Post.initWithPostID(postID, postDict: post)!
Post.feed?.append(newPost)
}
Post.feed? = (Post.feed?.reverse())!
self.tableView.reloadData()
}, withCancelBlock: { error in
print(error.localizedDescription)
})
}
Using only reverse() for your array is not enough way to encompass everything. There are different things you need to think about:
Limit while retrieving data, use append() and then reverse() to save time. You don't need to delete all array for each time.
Scroll trigger or willDisplay cell method loading
Let's start. You can create a child for your posts timestamp or date/time being global. To provide like Instagram seconds, weeks I advice you using UTC time. So I will call this: (timeUTC)
For sorting your all post, use since1970 timestamp. So I will call this (timestamp) and then also you can keep another node as (reversedTimestamp) adding - prefix to timestamp. So when you use queryOrdered to this node. You can handle latest 5 post using with yourQuery.queryLimited(toFirst: 5).
1.Get UTC date/time for timeUTC node in Swift 3:
let date = Date()
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
formatter.timeZone = TimeZone(abbreviation: "UTC")
let utcTimeZoneStr = formatter.string(from: date)
+0000 means it's universal time, look at http://time.is/tr/UTC
2.Get since1970 timestamp to sort posts in Swift 3:
let timestamp = (Date().timeIntervalSince1970 as NSString).doubleValue
let reversedTimestamp = -1.0 * timestamp
Now, you can save them on your Firebase posts like this.
"posts" : {
"-KHLOy5mOSq0SeB7GBXv" : {
"timestamp": "1475858019.2306"
"timeUTC" : "2012-02-04 12:11:56 +0000"
},
"-KHLrapS0wbjqPP5ZSUY" : {
"timestamp": "1475858010.1245"
"timeUTC" : "2014-02-04 12:11:56 +0000"
},
I will retrieve five by five post, so I'm doing queryLimited(toFirst: 5) in viewDidLoad:
let yourQuery = ...queryOrdered(byChild: "reverseTimestamp")
.queryEnding(atValue: "\(self.pageOnTimestamp)", childKey: "reverseTimestamp")
yourQuery.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.value is NSNull {
print("There is no post.")
}
else {
yourQuery.queryLimited(toFirst: 5).observeSingleEvent(of: .value, with: { (snapshot) in
self.posts.removeAll(keepingCapacity: true)
for (i, snap) in snapshot.children.enumerated() {
if let postAllDict = snapshot.value as? [String: AnyObject] {
if let postDict = postAllDict[(snap as AnyObject).key as String] as? [String: AnyObject] {
let post = Post(key: (snap as AnyObject).key as String, postDict: postDict)
self.posts.append(post)
}
}
}
completion(true)
})
}
})
If user reached latest post, you can handle it with willDisplay method like below, then you can call loadMore function.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if self.posts.count - 1 == indexPath.row {
// call loadMore function.
}
}
In loadMore() function you can handle latest post's timestamp, then start-end query as with that, so you can easily continue with next first 5 posts while appending before array.
For Swift 3 conversion as nice formatted, take a look here: Swift 3 - UTC to time ago label, thinking 12h / 24h device time changes
Based on #tobeiosdev answer i define my data structure like this:
"posts" : {
"-KcOa8GXBl08V-WXeX8P" : {
"author" : "#giovanny.piñeros",
"hashtags" : [ "Hello", "World" ],
"text" : "Hola Mundo!!!!!",
"timestamp" : 5.08180914309278E8,
"title" : "Hello World",
"userid" : "hmIWbmQhfgh93"
}
As you can see i've added a timestamp attribute, when i query my data with a child added event, i pass that data to a post object, then i append that object to an array of posts from which my table view will feed:
self.posts.append(post)
self.posts.sort(by: {$0.timestamp! > $1.timestamp!})
self.tableViewFeed.reloadData()
With the sort method i've managed to order my data in the desire order of posts, from the newest to the oldest. I Hope this approach could help anyone :).
In my Application I create an array of "NSDate" in order to send local notifications.
The values saved are "UUID" and "deadline" and they are saved using let gameDictionary = NSUserDefaults.standardUserDefaults().dictionaryForKey(GAME_INFO) ?? [:]
The result is somenting similar to this:
[{
UUID = "546C5E4D-CFEE-42F3-9010-9936753D17D85";
deadline = "2015-12-25 15:44:26 +0000";
}, {
UUID = "7C030614-C93C-4EB9-AD0A-93096848FDC7A";
deadline = "2015-12-25 15:43:15 +0000";
}]
What I am trying to achieve is to compare the "deadline" values with the current date and if the deadline is before than current date the values need to be removed from the array.
func compareDeadline() {
let gameDictionary = NSUserDefaults.standardUserDefaults().dictionaryForKey(GAME_INFO) ?? [:]
var items = Array(gameDictionary.values)
for i in 0..<items.count {
let dateNotification = items[i]["deadline"]!! as! NSDate
print(dateNotification)
var isOverdue: Bool {
return (NSDate().compare(dateNotification) == NSComparisonResult.OrderedDescending) // deadline is earlier than current date
}
print(isOverdue)
if (isOverdue == true){
items.removeAtIndex(i)
}
}
}
When I try to remove the values from the array I get Fatal Error: Array index out of range
Any Idea How can I solve this?
You should use the .filter method on the array to remove anything that you don't want in that array. The result is a new array with just the filtered results.
.filter requires you to set the filter criteria in a closure that you send into it
Here is a good article on how to use it
You can use filter method of swift array
For example to filter even numbers in array:
func isEven(number: Int) -> Bool {
return number % 2 == 0
}
evens = Array(1...10).filter(isEven)
println(evens)
There are a few problems. The reason you are getting an error is because you cannot remove elements while iterating inside for-in block. You can filter the items with the following code:
func compareDeadline() {
let gameDictionary = NSUserDefaults.standardUserDefaults().dictionaryForKey(GAME_INFO) ?? [:]
let items = Array(gameDictionary.values)
let currentDate = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ssZ"
let filteredItems = items.flatMap({
guard let stringDeadline = $0["deadline"] as? String, let deadline = dateFormatter.dateFromString(stringDeadline) else {
return nil
}
return deadline
}).filter({
currentDate.compare($0) == .OrderedDescending
})
}