How long ago did the User create a post, NSDate(), Manipulating the format of the "time of post" created - ios

I am making a social app that saves its posts in user specific nodes , with that i am also saving the time of post in this format :-
Wednesday, July 20, 2016, 00:14
which i display with the post in the global feed of friends of the user.
Before 24 hours of that post , i want to display time of post on the feed as this :- "5 Hours Ago"
After 24 hours of that post time of post becomes something like this :- "Yesterday"...
After 48 hours of that post time of post becomes something like this :- "On 5 Aug"...
So far i have come up with these two options:-
1.) Change the time of the feed in the database, which i think would be much better option.
2.) Retrieve the time of post , iterate through MULTIPLE if conditions and set the time of post accordingly.
I would be able to implement the second option but i have no clue to how to go forward with option one
Given that my JSON tree is something like this
appname:{
users : {....
.....
user1 : {....
.....
postsCreated : {
post1 : {
..
timeofPost : ""Wednesday, Aug 5, 2016, 00:14""
}
}
}
}
}
I did stumble upon http://momentjs.com/ but thats for Javascript
Also any suggestion on my JSON tree or is it fine the way it is?

You propose:
Change the time of the feed in the database, which i think would be much better option.
No, the date in the database, as well as that which is communicated with web service, should not be a formatted string. The database and the web service should be capturing the raw dates (or, more accurately, RFC3339/ISO8601 format or seconds from some reference date). The formatting of the elapsed time in a string for the UI is the responsibility of the app.
Retrieve the time of post, iterate through MULTIPLE if conditions and set the time of post accordingly.
Yes, that's what you should do.
By the way, if you're going to omit the year, you probably have a fourth permutation which includes year if the date is more than one year in the past, e.g.:
func formattedPostDateString(date: NSDate) -> String {
let now = NSDate()
let elapsed = NSCalendar.currentCalendar().components([.Day, .Year], fromDate: date, toDate: now, options: [])
switch (elapsed.year, elapsed.day) {
case (0, 0):
return "\(elapsedFormatter.stringFromDate(date, toDate: now)!) \(agoDateString)"
case (0, 1):
return yesterdayString
case (0, _):
return "\(onDateString) \(lessThanOneYearFormatter.stringFromDate(date))"
default:
return "\(onDateString) \(moreThanOneYearFormatter.stringFromDate(date))"
}
}
Where
let onDateString = NSLocalizedString("On", comment: "prefix used in 'On 5 Aug'")
let agoDateString = NSLocalizedString("ago", comment: "suffix use in '4 hours ago'")
let yesterdayString = NSLocalizedString("Yesterday", comment: "showing 'date' where it's between 24 and 48 hours ago")
let elapsedFormatter: NSDateComponentsFormatter = {
let formatter = NSDateComponentsFormatter()
formatter.allowedUnits = [.Year, .Month, .Day, .Hour, .Minute, .Second]
formatter.unitsStyle = .Full
formatter.maximumUnitCount = 1
return formatter
}()
let lessThanOneYearFormatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = NSDateFormatter.dateFormatFromTemplate("MMM d", options: 0, locale: nil)
return formatter
}()
let moreThanOneYearFormatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateStyle = .MediumStyle
return formatter
}()
The only thing you need to do is to convert the string returned by the web service into NSDate object. To that end, the web service should probably return the post date in ISO 8601/RFC 3339 format (e.g. 2016-08-26T15:01:23Z format).
To create ISO8601/RFC3339 dates in Swift 2:
let isoDateFormatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
return formatter
}()
And then:
let string = isoDateFormatter.stringFromDate(date)
Or
let date = isoDateFormatter.dateFromString(string)
Or in iOS 10+ using Swift 3, you can use the new ISO8601DateFormatter:
let isoDateFormatter = ISO8601DateFormatter()

Related

How to calculate time (minutes) between two dates in swift?

What do we got: Date+time (format yyyy-mm-dd hh:mm a)
What are we looking for: Time difference in minutes
What operation: NewDate - OldDate
So, I wonder how I could accomplish above goal? I would like to format the date and time to US, regardless from which locale the user has. How can I do that?
Then I will save the 'oldTime' into UserDefaults, and use it for later calculation. The goal is to put the user on delay for 5 minutes and the calculations will be performed to determine if user should be on delay or not.
Just make a function that takes two dates and compares them like this.
import UIKit
func minutesBetweenDates(_ oldDate: Date, _ newDate: Date) -> CGFloat {
//get both times sinces refrenced date and divide by 60 to get minutes
let newDateMinutes = newDate.timeIntervalSinceReferenceDate/60
let oldDateMinutes = oldDate.timeIntervalSinceReferenceDate/60
//then return the difference
return CGFloat(newDateMinutes - oldDateMinutes)
}
//Usage:
let myDateFormatter = DateFormatter()
myDateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
//You'll need both dates to compare, you can get them by just storing a Date object when you first start the timer.
//Then when you need to check it, compare it to Date()
let oldDate: Date = myDateFormatter.date(from: String("2019-06-22 11:25"))
func validateRefresh() {
//do the comparison between the old date and the now date like this.
if minutesBetweenDates(oldDate, Date()) > 5 {
//Do whatever
}
}
You can, of course, change the .dateFormat value on the date formatter to be whatever format you'd like. A great website for finding the right format is: https://nsdateformatter.com/.
You say:
I would like to format the date and time to US, regardless from which locale the user has. How can I do that?
Specify a Locale of en_US_POSIX:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd hh:mm a"
formatter.locale = Locale(identifier: "en_US_POSIX")
The locale is not the only question.
There’s also a timezone question. For example, you're driving out of Chicago and go from Central to Eastern timezones; do you really want to consider that one hour has passed?
Do you really want to discard seconds? If you do that, the 59 seconds between going from 8:00:00pm to 8:00:59pm will be considered “zero minutes” but the one second between 8:00:59pm and 8:01:00pm will be considered “one minute”.
Frankly, if I wanted to save a locale and timezone invariant date string, I’d suggest using ISO8601DateFormatter.
Then I will save the 'oldTime' into UserDefaults, and use it for later calculation.
If that’s why you’re using this DateFormatter, I’d suggest saving the Date object directly.
UserDefaults.standard.set(oldTime, forKey: "oldTime")
And to retrieve it:
if let oldTime = UserDefaults.standard.object(forKey: "oldTime") as? Date {
...
}
In terms of calculating the number of minutes between two Date objects
let minutes = Calendar.current
.dateComponents([.minute], from: date1, to: date2)
.minute
If you want the number of seconds, you can also use timeIntervalSince:
let seconds = date2.timeIntervalSince(date1)
And if you wanted to show the amount of elapsed time as a nice localized string:
let intervalFormatter = DateComponentsFormatter()
intervalFormatter.allowedUnits = [.minute, .second]
intervalFormatter.unitsStyle = .full
let string = intervalFormatter.string(from: date1, to: date2)
I'm not convinced that your question is the best way to go about accomplishing your aim, but the code below will work.
let dateFormatterNow = DateFormatter()
dateFormatterNow.dateFormat = "yyyy-MM-dd hh:mm a"
dateFormatterNow.timeZone = TimeZone(abbreviation: "EST")
let oldDateString = "2019-06-23 12:44 p"
let oldDate = dateFormatterNow.date(from: oldDateString)
let newDateString = "2019-06-23 12:54 p"
let newDate = dateFormatterNow.date(from: newDateString)
if let oldDate = oldDate, let newDate = newDate {
let diffInMins = Calendar.current.dateComponents([.minute], from: oldDate, to: newDate).minute
print(diffInMins)
}

Swift 4 : Set Different Date and Time

I know how to get local date and time, but what I want to do is getting the date and time from different places. For example, I want to find out what the time and date is in New York. How can i solve this simple problem?
Here is my code for local date and time :
let date = NSDate()
let calendar = Calendar.current
let components = calendar.dateComponents([.hour, .minute, .month, .year, .day, .second, .weekOfMonth], from: date as Date)
let currentDate = calendar.date(from: components)
I searched about it here, but i didn't find what i want and I'm still looking for the date libaries. If you know any source or sample to redirect me, I really appreciate that.
There are several different concepts involved here, and we need to understand (almost) all of them to get this right...
1) a Date (NSDate as was, in Swift) is an absolute point in time - it's slightly mis-named, because it has nothing to do with an actual date like 13th November 2017, because to get to that we need to define ...
2) a Calendar, because 13th November 2017 in the western Gregorian calendar could also be 23rd Safar 1439 in the Islamic calendar, or the 24th of Heshvan 5778 in the Hebrew calendar, or some other things in the many other calendars that iOS & MacOS support;
3) in turn Calendar changes not only what values are returned in the DateComponents that we have to use to unpack a Date + Calendar into days, months, years & eras (e.g. BC/AD), or even week number, etc..., but also some calendars might not have the same components as others;
4) time-of-day (as you know) depends on TimeZone, so the same absolute time can be one of many different times "o'clock" depending on where you are. It may also (as you can see in the example below) change the date as well as the "o'clock". This of course could be automatic (where you are) or set by the programmer;
5) further, we have DateFormatter (which is a convenience that wraps up DateComponents), because 13th November 2017 could be represented as 13/11/17 or 11/13/17 depending on whether you are British or American. We may also wish to choose whether we use text or numeric months, and, if displaying times, whether we want 12 hour or 24 hour format - all of these are covered by DateFormatter, but text representation may be "13e Novembre 2017" if you are French, which introduces the notion of
6) Locale, which can be set, like TimeZone, as being default (as chosen when you set up the device) or specified by the programmer.
The code you posted won't work, because all it does is takes a Date, transforms it through a Calendar to DateComponents (all good so far), but then recreates a Date from the components - all you will get is the original Date - the same absolute point in time.
What I believe from the question and your answers to questions in the comments is that you want a function that takes an absolute time (eg "now") aka a Date and displays it in a specific TimeZone. This works:
func timeComponents(date: Date, timeZone: TimeZone) -> DateComponents {
var calendar = Calendar.current
calendar.timeZone = timeZone
return calendar.dateComponents([.hour, .minute, .month, .year, .day, .second, .weekOfMonth], from: date)
}
let absTime: Date = Date() // Now
let edinburgh = TimeZone(abbreviation: "GMT")!
let newYork = TimeZone(abbreviation: "EST")!
let ec = timeComponents(date: absTime, timeZone: edinburgh)
let nycc = timeComponents(date: absTime, timeZone: newYork)
print(ec)// year: 2017 month: 11 day: 14 hour: 0 minute: 44 second: 10 weekOfMonth: 3 isLeapMonth: false
print(nycc) // year: 2017 month: 11 day: 13 hour: 19 minute: 44 second: 10 weekOfMonth: 3 isLeapMonth: false
... which I think answers the minimum of your question, but to finesse it, we need to move from DateComponents to DateFormatter
func timeString(date: Date, timeZone: TimeZone, timeStyle: DateFormatter.Style) -> String {
let dateFormatter = DateFormatter()
dateFormatter.timeZone = timeZone
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = timeStyle
return dateFormatter.string(from: date)
}
let es = timeString(date: absTime, timeZone: edinburgh, timeStyle: .full)
let nycs = timeString(date: absTime, timeZone: newYork, timeStyle: .full)
print(es) // 12:44:10 AM Greenwich Mean Time
print(nycs) // 7:44:10 PM Eastern Standard Time
You can go on, and start to use Locale, if you want to internationalise your app, but I'l leave that as an exercise!
p.s. These are not all of the concepts - see here
p.p.s. See also this answer and this answer (neither duplicates)
If you just want to format the date to a string, consider using a DateFormatter instead:
let date = Date()
let formatter = DateFormatter()
formatter.timeZone = TimeZone(identifier: "America/New_York")
formatter.dateStyle = .long
formatter.timeStyle = .long
formatter.string(from: date)
If you want to get the date components and process them, use the dateComponents(in:from:) method.
let components = Calendar.current.dateComponents(in: TimeZone(identifier: "America/New_York")!, from: date)
If you don't know the time zone of the place you are searching for, you can use the CoreLocation's CLGeocoder and search on an address string. Then you can get the timezone for that place and translate that into the time you're looking for:
let geocoder = CLGeocoder()
geocoder.geocodeAddressString("New York, New York") { (placemarks, error) in
guard error == nil else {
print("Error")
print(error!.localizedDescription)
return
}
guard let placemarks = placemarks,
let place = placemarks.first else {
print("No results")
return
}
if let timeZone = place.timeZone {
print("TimeZone: \(timeZone.identifier)")
// TimeZone: America/New_York
//Ignore the time zone offset from this one, it will be the difference between the current time and the new york time
let dateInNewYork = Date().addingTimeInterval(TimeInterval.init(timeZone.secondsFromGMT()))
print(dateInNewYork)
// 2017-11-13 15:03:05 +0000
//Or
let formatter = DateFormatter()
formatter.timeZone = TimeZone(identifier: timeZone.identifier)
formatter.dateStyle = .long
formatter.timeStyle = .long
let formattedDateInNewYork = formatter.string(from: Date())
print(formattedDateInNewYork)
// November 13, 2017 at 3:03:05 PM EST
//Or
let components = Calendar.current.dateComponents(in: TimeZone(identifier: timeZone.identifier)!, from: Date())
print(components.date!)
// 2017-11-13 20:03:05 +0000
}
}

Date formatting for next available date in Swift [duplicate]

This question already has answers here:
Convert string with unknown format (any format) to date
(2 answers)
Closed 5 years ago.
I'm currently downloading fixture lists for a sports app from a third party website that runs the league, so the data I have to work with is restricted.
I'm trying to implement a feature that displays the next upcoming fixture.
My problem is the dates being retrieved look like this:
"Sat 9th Sep 17" and "Sat 24th Mar 18" for example.
I've tried numerous date formats in the DateFormatter that I know of and can't find anything that uses this specific format.
If I try to use the Date from string method in the date formatter with one of the above strings I get a nil value.
I have an array of fixtures, each with their own dates. I need to compare each of these to the current date to figure out which is next in line.
My temporary fix for this was just to loop through the fixtures and as soon as one did not have a result to display that as the next fixture. Obviously this doesn't work when a game may not have been played for whatever reason.
What would be the best way to deal with this?
Basically you would just need to convert the current date to the same format as the date you get from your third party website (or the opposite) so you can compare them easily:
let currentDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EE MMM"
let firstPartStringDate = dateFormatter.string(from: currentDate)
dateFormatter.dateFormat = "yy"
let lastPartStringDate = dateFormatter.string(from: currentDate)
let day = Calendar.current.component(.day, from: currentDate)
let formatter = NumberFormatter()
formatter.numberStyle = .ordinal
guard let ordinalDay = formatter.string(for: day) else {
return
}
let finalDateString = firstPartStringDate + " \(ordinalDay) " + lastPartStringDate
print(finalDateString)
And for today's current date you would get the exactly same format as the one you get from the third-party website :
Sun Sep 17th 17
UPDATE: Here is how you could convert the String you get from the third-party website to a Date, and then compare it with the current date. This solves the problem of having the st, nd, rd and th inside the String at first.
// This is the string you get from the website
var webDateString = "Sat 9th Sep 17"
// First, remove the st, nd, rd or th if it exists :
if let stSubrange = webDateString.range(of:"st") {
webDateString.removeSubrange(stSubrange)
} else if let ndSubrange = webDateString.range(of:"nd") {
webDateString.removeSubrange(ndSubrange)
} else if let rdSubrange = webDateString.range(of:"rd") {
webDateString.removeSubrange(rdSubrange)
} else if let thSubrange = webDateString.range(of:"th") {
webDateString.removeSubrange(thSubrange)
}
// Now convert the string to a date :
dateFormatter.dateFormat = "EE MMM dd yy"
guard let formattedDate = dateFormatter.date(from: finalDateString) else {
return
}
// You can now compare formattedDate and the current date easily like so :
let currentDate = Date()
if formattedDate < currentDate {
// Do something interesting here :)
} else {
// Do something else!
}

Same Date... But Different? Maybe timezone confusion

My problem is that I save a date into a string and as a date in CoreData. Later, I need to pull the date out of the string, compare the two, and find that they're the same date. Right now, that equality check fails. The two dates are 7 hours apart but with the minutes correct. I think it's a timezone issue but I can't figure out how to solve it.
The Origin of the Dates
I have a date from a date picker that I save to CoreData like this:
task.setValue(dueDatePicker.date, forKey: "dueDate")
After that I format the date and insert that date into a message:
let dateFormatter = DateFormatter()
let dateFormat = DateFormatter.Style.medium
let timeFormat = DateFormatter.Style.short
dateFormatter.dateStyle = dateFormat
dateFormatter.timeStyle = timeFormat
let formattedDate = dateFormatter.string(from: date)
let message = ("Upcoming task on \(formattedDate)")
That message becomes part of a notification. Hours or days later (when the notification fires and the user selects an action) I get the CoreData date:
fetchRequest.predicate = NSPredicate(format: "dueDate = %#", dateOfTask)
Then I decompose the notification message and get the date:
let start = notifString.range(of: "on ")
let rawDate = notifString[(start.upperBound)!..<(notifString.endIndex)]
let dateFormatter = DateFormatter()
dateFormatter.timeZone =
dateFormatter.dateFormat = "MMM-d-yyyy, H:mm a"
let dateFromString = dateFormatter.date(from: rawDate)
Lastly, I compare them. Currently the times are clearly the same day and minute but the timezones differ by about 7 hours. However, I don't want to just force a timezone that matches (Maybe force UTC for example) because that may not work for a user in another location.
How do I retrieve both dates without getting this apparent timezone issue?
Blatantly obvious, use userInfo as #Paulw11 suggested:
newLocalNotif.fireDate = dueDateWarningTime
newLocalNotif.alertBody = message
newLocalNotif.timeZone = TimeZone.autoupdatingCurrent
newLocalNotif.soundName = UILocalNotificationDefaultSoundName
newLocalNotif.category = "DueDates"
newLocalNotif.userInfo = ["name": name, "desc": desc, "dueDate" : date]

Toggle a stored flag on a weekly basis

I have a value I would like to switch to another once a week either in the background on when the user launches the app on whichever day even if it's more than a week later. As the user uses the app during the week, each day turns a value e.g. variable b to false but every once a week I want to change that variable back to true for any day that it's been turned to false. I have a function I tried putting in the AppDelegate's didFinishLaunchingWithOptions but I am not precisely sure how to do the check and hence it didn't work. This is my attempt:
func resetOnSunday() {
let date = NSDate()
let formatter = NSDateFormatter()
let timeFormatter = NSDateFormatter()
formatter.dateFormat = "EEEE"
let WeekDay = formatter.stringFromDate(date)
timeFormatter.dateFormat = "HH:mm a"
let time = timeFormatter.stringFromDate(date)
if time >= "00:00 AM" && time <= "00:02 AM" && WeekDay == "Sunday" {
var b = true
}
}
Does anyone know how it can be done?
First of all, you can significantly cut down your code (and make it a lot more reliable) by using NSCalendar.
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
//This will return a NSDate optional which you can check against.
let nextSunday = calendar.nextDateAfterDate(date, matchingUnit: .Weekday, value: 1, options: .MatchNextTime)
if date.timeIntervalSinceDate(nextSunday!) > 0 {
//Do your stuff here
}
Note that:
Weekday units are the numbers 1 through n, where n is the number of days in the week. For example, in the Gregorian calendar, n is 7 and Sunday is represented by 1.
Next, I would use NSUserDefaults for your value:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setBool(true, forKey: "yourVar")
//Then to read it
defaults.boolForKey("yourVar")

Resources