Swift: Substring based on a character - swift

I'm getting information from an API and one of the fields is a DateTime in this format:
2014-12-12T14:44:18.973
I would like to remove the "T" and anything after it so in the end, I get:
2014-12-12
Any ideas or leads on how to do this with Swift? Unfortunately, I can't just create an NSDate out of directly as it throws an Exception.
In the end, I want to have something like 12-12-2014 in a string format.
Thanks!

Fortunately, You CAN just create an NSDate out of it directly as follow:
extension String {
func toDateFormattedWith(format:String)-> NSDate {
let formatter = NSDateFormatter()
// formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) // you can set GMT time
formatter.timeZone = NSTimeZone.localTimeZone() // or as local time
formatter.dateFormat = format
return formatter.dateFromString(self)!
}
}
There is many ways to pick just the date from that string. I would use the following approach:
let myDate = "2014-12-12T14:44:18.973".componentsSeparatedByString("T")[0].toDateFormattedWith("yyyy-MM-dd") // "Dec 12, 2014, 12:00 AM"
You can also create an extension to extract that info from the Date object:
public extension NSDate {
var day: Int { return NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitDay, fromDate: self).day }
var month: Int { return NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitMonth, fromDate: self).month }
var year: Int { return NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitYear, fromDate: self).year }
var dateAt12am: NSDate {
return NSCalendar.currentCalendar().dateWithEra(1, year: year, month: month, day: day, hour: 0, minute: 0, second: 0, nanosecond: 0)!
}
}
let myDate1 = "2014-12-12T14:44:18.973".toDateFormattedWith("yyyy-MM-dd\'T\'HH:mm:ss.SSS").dateAt12am
println(myDate1) // "2014-12-12 02:00:00 +0000" (12am local time)
If you need reference you can use this:

This actually isn't too bad. What you're looking for is rangeOfString() to find out where "T" is. If you call .startIndex on that, you can find out where the first occurrence of that "T" is.
Once you have that, you can just create a substring from the beginning of the string to that point like this:
let date: String = dateTime.substringWithRange(Range<String.Index>(start: dateTime.startIndex, end: dateTime.rangeOfString("T")!.startIndex))
If you wanted to take that and turn it into an NSDate, you could use an NSDateFormatter:
let formatter: NSDateFormatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let newDate: NSDate = formatter.dateFromString(date)! // "Dec 12, 2014, 12:00 AM"
If you don't want that time piece at the end and just want to output the date in a different style than that returned by the API, you could add this:
formatter.dateStyle = .ShortStyle // Change this to modify the style of the date that is returned in the next line
let formattedDate: String = formatter.stringFromDate(newDate) // "12/12/14"

Related

How to change the hours and minutes in an existing Date object in Swift?

I need to compare two Date object to get the day difference between them, for example: 10/10 compares with today 10/7 will be 3, but the Date object returned to me from server is not aligned with the current time. There will be a few minutes difference which results in 10/10 being 2 days ahead of 10/7 because of the delay
I found a line of code that can give me a Date object of the current time, but I want to convert an existing Date object from somewhere else, how do I do it?
let today = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date())!
e.g. 2020-10-08 16:08:57.259580+0000 I want it to be 2020-10-08 00:00:00 +0000 something like this
Don’t use midnight. Just parse your date string first. For calendrical calculations you should always use noon. First create a custom date format to parse your date string properly.
extension Formatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = .init(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSSxx"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
}
Then create a helper to convert your date to noon time and another one to calculate the days between two dates and set them to noon:
extension Date {
var noon: Date {
Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
}
func days(from date: Date) -> Int {
Calendar.current.dateComponents([.day], from: date.noon, to: noon).day!
}
var daysFromToday: Int { days(from: Date()) }
}
Playground testing:
let dateString = "2020-10-08 16:08:57.259580+0000"
let now = Date() // "Oct 8, 2020 at 5:56 AM"
print(Formatter.iso8601.string(from: now)) // "2020-10-08 08:56:46.179000+0000\n"
if let date = Formatter.iso8601.date(from: dateString) { // "Oct 8, 2020 at 1:08 PM"
let days = Date().days(from: date) // 0
}
let dateString = "2020-10-10 16:08:57.259580+0000"
if let date = Formatter.iso8601.date(from: dateString) {
let days = date.daysFromToday // 2
}

Swift: Convert Array of String dates into Formatted String dates

So I have an array of dates that are already formatted as strings. But I want to further format it. What I have currently prints me nil. I want to be able to convert it into something like "June, 23, 2020" or "June-23", etc. What am I missing?
let dates = ["2020-06-23", "2021-06-24", "2022-06-25", "2020-06-26", "2020-06-29"]
for i in dates {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd"
let date = dateFormatter.date(from: i)
print(date)
}
First you get the right format. Then you can change it.
You can use
let dates = ["2020-06-23", "2021-06-24", "2022-06-25", "2020-06-26", "2020-06-29"]
//create an empty array of dates
var datesFormated = [Date]()
let initialDateFormatter = DateFormatter()
initialDateFormatter.dateFormat = "yyyy-MM-dd"
for i in dates {
let date = initialDateFormatter.date(from: i)!
//but you have to make sure that the date format of the strings match the date formatter, otherwise it will crash
datesFormated.append(date)
}
//then change the format to the one you need
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM d, yyyy"
for date in datesFormated {
let date = dateFormatter.string(from: date)
print(date)
}
Output would be
Jun 23, 2020
Jun 24, 2021
Jun 25, 2022
Jun 26, 2020
Jun 29, 2020
Hope this helps.
Try with:
let dates = ["2020-06-23", "2021-06-24", "2022-06-25", "2020-06-26", "2020-06-29"]
var datesFormated = [Date]()
let initialDateFormatter = DateFormatter()
let dateFormatter = DateFormatter()
initialDateFormatter.dateFormat = "yyyy-MM-dd"
dateFormatter.dateFormat = "MMM d, yyyy"
dates.forEach {
datesFormated.append( initialDateFormatter.date(from: $0)! )
}
datesFormated.forEach {
print(dateFormatter.string(from: $0))
}
Output:
Jun 23, 2020
Jun 24, 2021
Jun 25, 2022
Jun 26, 2020
Jun 29, 2020
I was writing this as you posted your answer, but I'll post anyway as there's a slight difference in approaches.
I suggest using both DateFormatter.date(from: string) AND DateFormatter.string(from: date). In other words, build two distinct date formatters: one to read in your date in "yyyy-MM-DD" format, and another to make a string in a different format. You can then change each of the formatters as needed, or even have a method wrapping this code where you can insert different formatters or format strings, making it flexible if you want to change formats.
Also, there's no need to instance a new formatter every time you go through the loop.
let dates = ["2020-06-23", "2021-06-24", "2022-06-25", "2020-06-26", "2020-06-29"]
// use this formatter to read in dates from initial strings
let originalDateFormatter = DateFormatter()
originalDateFormatter.dateFormat = "yyyy-MM-dd"
// use this formatter to generate new strings from dates
let newDateFormatter = DateFormatter()
newDateFormatter.dateFormat = "MMMM d" // or choose whatever new string format you want
for i in dates {
if let date: Date = originalDateFormatter.date(from: i) {
let dateInNewStringFormat: String = newDateFormatter.string(from: date)
print(dateInNewStringFormat) // prints as "June 23"
}
}
First of all, initializing DateFormatter on every iteration is very wasteful. Make it constant or static, and only change format when needed.
Secondly, when working with DateFormatter, you should always think of it as transformation where date to string, or vice versa, but you cannot transform one string to another directly. In your case, you need:
string > date > string
So basic code would look like this:
let dates = ["2020-06-23", "2021-06-24", "2022-06-25", "2020-06-26", "2020-06-29"]
let dateFormatter = DateFormatter()
for i in dates {
// string > date
dateFormatter.dateFormat = "YYYY-MM-dd"
guard let fromDate = dateFormatter.date(from: i) else {
// not a valid date, skip
continue
}
// date > string
dateFormatter.dateFormat = "MMM, YYYY" // or whatever you need
let toDate = dateFormatter.string(from: fromDate)
print(toDate)
}
But also, if you have this same formatting in multiple places in your code, it's best to create an extension:
extension Date {
static let dateFormatter = DateFormatter()
// If you are dealing with the same date as input often, you can do this:
init?(fromDateOnlyString: String) {
Date.dateFormatter.dateFormat = "YYYY-MM-dd"
guard let date = Date.dateFormatter.date(from: fromDateOnlyString) else {
return nil
}
self = date
}
// If you need the same output format often, you can do this:
func toMyDesiredFormat() -> String {
Date.dateFormatter.dateFormat = "MMM, YYYY" // Or whatever you need
return Date.dateFormatter.string(from: self)
}
}
And you use this extension like this:
for i in dates {
guard let fromDate = Date(fromDateOnlyString: i) else {
// not a valid date, skip
continue
}
let toDate = fromDate.toMyDesiredFormat()
print(toDate)
}
Found the solution. You have you first convert it into Dates then convert those Dates into Formatted strings again. I had the format for the initial dateFormatter wrong as "dd".
let dates = ["2020-06-23", "2020-06-24", "2020-06-25", "2020-06-26", "2020-06-29"]
var formattedDates = [Date]()
for i in dates {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
if let date = dateFormatter.date(from: i) {
formattedDates.append(date)
}
}
print(formattedDates) //prints [2020-06-23 04:00:00 +0000, 2020-06-24 04:00:00 +0000, 2020-06-25 04:00:00 +0000, 2020-06-26 04:00:00 +0000, 2020-06-29 04:00:00 +0000]

Get array of dates between today and given date in Swift?

I have a date in string format, "yyyy-MM-dd" and would like to return an array of the difference in dates in that same format from today.
For example, the given date is "2019-06-29", and today's date is 2019-06-25. The returned array would contain: ["2019-06-25", "2019-06-26", "2019-06-27", "2019-06-28", "2019-06-29"].
The method I am trying to write needs to also work cross-months/years. Is something like this possible in Swift?
What I have tried: calculating the difference in dates numerically (difference of days) and adding a day to the given date until it reaches today's date. This is what brought on the issue of exceeding 30/31 days and not moving to the next months/exceeding 2019-12-31 and not moving to 2020. Surely there is a simpler concise way to achieve this result without having to write that date logic manually?
extension Formatter {
static let date: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .iso8601)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter
}()
}
extension Date {
var noon: Date {
return Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
}
}
func dates(for date: String) -> [String] {
// For calendrical calculations you should use noon time
// So lets get endDate's noon time
guard let endDate = Formatter.date.date(from: date)?.noon else { return [] }
// then lets get today's noon time
var date = Date().noon
var dates: [String] = []
// while date is less than or equal to endDate
while date <= endDate {
// add the formatted date to the array
dates.append( Formatter.date.string(from: date))
// increment the date by one day
date = Calendar.current.date(byAdding: .day, value: 1, to: date)!
}
return dates
}
dates(for: "2019-06-29") // ["2019-06-25", "2019-06-26", "2019-06-27", "2019-06-28", "2019-06-29"]

How to convert a String to NSdate?

I am trying to convert fajerTime to NSDate. When I compile the project the dateValue is nil. Any idea how to fix this issue?
if prayerCommingFromAdan.id == 0 && prayerCommingFromAdan.ringToneId != 0{
// NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(YourClassName.methodOfReceivedNotification(_:)), name:"NotificationIdentifier", object: nil)
let fajerTime = "\(prayer0.time[0...1]):\(prayer0.time[3...4])" as String
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
dateFormatter.timeZone = NSTimeZone.localTimeZone()
// convert string into date
let dateValue = dateFormatter.dateFromString(fajerTime) as NSDate!
print(dateValue)
var dateComparisionResult:NSComparisonResult = NSDate().compare(dateValue)
if dateComparisionResult == NSComparisonResult.OrderedDescending {
addNotificationAlarm(year, month: month, day: day, hour: prayer0.time[0...1], minutes: prayer0.time[3...4], soundId: prayerCommingFromAdan.ringToneId, notificationBody: "It is al fajr adan")
}
The problem seems be the format of fajerTime. It looks like fajerTime is a time string, e.g. 12:34, whereas the date formatter is configured to accept string containing a month, day and year, e.g. 24-07-2016.
You need to format fajerTime to include the year, month and day, as well as the time. Also configure the date formatter to accept the full date and time.
Assuming prayer0 is an array, you will also need to combine the elements into a string, using joinWithSeparator.
e.g.
let hours = prayer0.time[0...1].joinWithSeparator("")
let minutes = prayer0.time[3...4].joinWithSeparator("")
let fajerTime = "\(month)-\(day)-\(year) \(hours):\(minutes)"
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy hh:mm"
dateFormatter.timeZone = NSTimeZone.localTimeZone()
// convert string into date
let dateValue = dateFormatter.dateFromString(fajerTime) as NSDate!
Please follow example (Swift 3):
let dateStr = "2016-01-15 20:10:01 +0000"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let myDate = dateFormatter.date(from: dateStr)
Keep in mind:
Date format e.g. "yyyy-MM-dd HH:mm:ss Z" must match the date string pattern
In your case, your date string contains only time, yet, your date
formatter contains only date
When using dateFormatter.date() there is no need to cast it to Date as it returns a Date:
Helpful website for date formats:
http://nsdateformatter.com/

Number to Date - Swift

Getting the day of year is straightforward, e.g.
func dayOfYear(inputDate:NSDate) -> (Int) {
let cal = NSCalendar.currentCalendar()
let returnDay = cal.ordinalityOfUnit(.CalendarUnitDay, inUnit: .CalendarUnitYear, forDate: inputDate)
return returnDay
}
But how do you do the reverse? It would obviously return just the day/month. I can easily write a tedious routine back-calculating but is there a smart way?
Yes, NSCalendar provides a way to coalesce calendar components into a single date object. Take a look at this example I wrote in a Playground:
import UIKit
import Foundation
let inputDate: NSDate = NSDate()
let calendar = NSCalendar.currentCalendar()
let day = calendar.ordinalityOfUnit(.CalendarUnitDay, inUnit: .CalendarUnitYear, forDate: inputDate)
let components = NSDateComponents()
components.day = day
let date = calendar.dateFromComponents(components)
According to the documentation,
When there are insufficient components provided to completely specify an absolute time, a calendar uses default values of its choice. When there is inconsistent information, a calendar may ignore some of the components parameters or the method may return nil. Unnecessary components are ignored (for example, Day takes precedence over Weekday and Weekday ordinals).
Furthermore,
Note that some computations can take a relatively long time to perform.
See the NSCalendar Class Reference for more information.
func dayOfYear(inputDate: NSDate) -> Int {
return NSCalendar.currentCalendar().ordinalityOfUnit(.CalendarUnitDay, inUnit: .CalendarUnitYear, forDate: inputDate)
}
func dateFromDayOfYear(day: Int) -> NSDate {
return NSCalendar.currentCalendar().dateWithEra(1, year: NSCalendar.currentCalendar().component(.CalendarUnitYear, fromDate: NSDate()), month: 1, day: day, hour: 0, minute: 0, second: 0, nanosecond: 0)!
}
let numberOfDays = dayOfYear(NSDate()) // 195
dateFromDayOfYear(numberOfDays) // "Jul 14, 2015, 12:00 AM"