Using a NumberFormatter within a DateFormatter - swift

I'm attempting to format my Date() to look like Saturday, June 12th • 5PM - 12PM. I've been able to solve the majority of this with the following DateFormatter():
var date_formatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "EEEE, MMMM d • HHa - HHa"
return formatter
}
Which results in Saturday, June 12 • 5PM - 12PM
The challenge I'm having is understanding how to add the ordinal suffix (i.e. 12 -> 12th). I've seen a bit on the NumberFormatter(), but am not entirely sure how to integrate the two.
EDIT: Ended up having to create two formats for the 5PM - 12PM logic.
This looks like:
var start_time_formatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "EEEE, MMMM d • HHa -"
return formatter
}
var end_time_formatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "HHa"
return formatter
}
with the following to display it in a view:
Text("\(self.create_event_vm.start_time, formatter: self.start_time_formatter) \(self.create_event_vm.end_time, formatter: self.end_time_formatter)")
I understand this is a bit funky and could use some refactoring, but I'm hoping to get the desired effect, test, then refactor.

First for the day suffix you can create below function
func getDaySuffix(from date: Date) -> String {
switch Calendar.current.component(.day, from: date) {
case 1, 21, 31: return "st"
case 2, 22: return "nd"
case 3, 23: return "rd"
default: return "th"
}
}
and combine with your codes :
let startDate = Date()
let endDate = Calendar.current.date(byAdding: .hour, value: 5, to: startDate)!
var startTimeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "EEEE, MMMM d'\(getDaySuffix(from: startDate))' • ha - "
return formatter
}
var endTimeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "ha"
return formatter
}
let startDateResult = startTimeFormatter.string(from: startDate) // "Thursday, June 18th • 2AM - "
let endDateResult = endTimeFormatter.string(from: endDate) // "7AM"
let finalResult = startDateResult + endDateResult // "Thursday, June 18th • 2AM - 7AM"

First a helpful extension Int+Ordinal.swift:
extension Int {
/// `Int` ordinal suffix
enum Ordinal: String {
/// Suffix for numbers ending in `1` except 11
case st = "st"
/// Suffix for numbers snding in `2` except 12
case nd = "nd"
/// Suffix for numbers ending in `3` except 13
case rd = "rd"
/// Suffix otherwise
case th = "th"
}
/// Get `Ordinal` from `Int` `self`
var ordinal: Ordinal {
var mod = self % 100
if mod == 11 || mod == 12 || mod == 13 {
return .th
} else {
mod = mod % 10
if mod == 1 {
return .st
} else if mod == 2 {
return .nd
} else if mod == 3 {
return .rd
} else {
return .th
}
}
}
}
Then using this extension and DateFormatter:
func string(from startDate: Date, to endDate: Date) -> String {
let formatter = DateFormatter()
// weekdayMonth
formatter.dateFormat = "EEEE, MMMM"
let weekdayMonth = formatter.string(from: startDate)
// day
formatter.dateFormat = "d"
var day = formatter.string(from: startDate)
if let dayValue = Int(day) {
day += dayValue.ordinal.rawValue
}
// timeFrom
formatter.dateFormat = "ha"
let timeFrom = formatter.string(from: startDate)
// timeTo
let timeTo = formatter.string(from: endDate)
return "\(weekdayMonth) \(day) • \(timeFrom) - \(timeTo)"
}
Running it:
let dateString = string(from: Date(), to: Date().addingTimeInterval(3600 * 4))
print(dateString) // "Wednesday, June 17th • 11PM - 3AM"
Though I guess in this example you don't want it running into the next day! :)

You are going through the wrong path. What you need is to create a DateInterval and use its DateIntervalFormatter to display it to the user. Note that 12PM it is already another day so your date interval representation is wrong:
let dateA = DateComponents(calendar: .current, year: 2020, month: 6, day: 12, hour: 17, minute: 0).date!
let dateB = DateComponents(calendar: .current, year: 2020, month: 6, day: 13, hour: 0, minute: 0).date!
let di = DateInterval(start: dateA, end: dateB)
let dif = DateIntervalFormatter()
dif.dateTemplate = "EEEEMMMMdh"
dif.string(from: di) // "Friday, June 12, 5 PM – Saturday, June 13, 12 AM"

Related

swift problem on calculate time difference

I have develop a function that calculate the time difference between two date giving the two date as string here below the function
func calculateTimeDifference(startDate: String, endDate: String) -> Int {
print("START DATE 1: \(startDate)")
print("END DATE 1: \(endDate)")
let startDate = dateTimeFormatter.date(from: startDate)
let endDate = dateTimeFormatter.date(from: endDate)
print("START DATE 2: \(startDate)")
print("END DATE 2: \(endDate)")
guard let startDate = startDate,
let endDate = endDate else {
print("return 1")
return 0
}
let dateDifference = Calendar.current.dateComponents([.minute], from: startDate, to: endDate)
let minuteDifference = dateDifference.minute
guard let minuteDifference = minuteDifference else {
print("return 2")
return 0
}
//timeDifference = minuteDifference
print("TIME DIFFERENCE: \(minuteDifference)")
return minuteDifference
}
and the corresponding date formatter that I'm using
var dateFormatter : DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .short
return formatter
}
var timeFormatter : DateFormatter {
let formatter = DateFormatter()
formatter.timeStyle = .short
return formatter
}
var dateTimeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "d, MMM y, HH:mm"
return formatter
}
also try too add before each return formatter: formatter.locale = Locale.current
so now the problem is if I try this code on the simulator it works perfectly this below is the output in console using the simulator:
START DATE 1: 13, mag 2022, 11:36
END DATE 1: 13, mag 2022, 12:06
START DATE 2: Optional(2022-05-13 09:36:00 +0000)
END DATE 2: Optional(2022-05-13 10:06:00 +0000)
when try on physical device the output is this:
START DATE 1: 13, mag 2022, 11:36
END DATE 1: 13, mag 2022, 12:06
START DATE 2: nil
END DATE 2: nil
I guess this related to different locale, as May is Maggio italian
var dateTimeFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "d, MMM y, HH:mm"
formatter.locale = .init(identifier: "it_CH") // for italian locale
return formatter
}
calculateTimeDifference(startDate: "13, mag 2022, 11:36", endDate: "13, mag 2022, 12:06")
and the results
START DATE 1: 13, mag 2022, 11:36
END DATE 1: 13, mag 2022, 12:06
START DATE 2: Optional(2022-05-13 09:36:00 +0000)
END DATE 2: Optional(2022-05-13 10:06:00 +0000)
TIME DIFFERENCE: 30

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
}

Get week ranges based off start date and number of given weeks

I have a start date of July 13, 2020 and an Int representing the number of weeks that I have chosen, in this case five. I would like to print each week range for those five weeks. Example of the expected output:
July 13 - July 19
July 20 - July 26
July 27 - August 2
August 3 - August 9
August 10 - August 16
With the code below, I'm able to get the first two weeks however, the weeks should continue to print depending on the input of weeks.
func getRanges() {
let arrWeekDates = rangeString!.getWeekDates() // Get dates of two weeks.
let dateFormat = "MMM dd"
let thisMon = arrWeekDates.thisWeek.first!.toDate(format: dateFormat)
let thisSun = arrWeekDates.thisWeek[arrWeekDates.thisWeek.count - 1].toDate(format: dateFormat)
let nextMon = arrWeekDates.nextWeek.first!.toDate(format: dateFormat)
let nextSun = arrWeekDates.nextWeek[arrWeekDates.nextWeek.count - 1].toDate(format: dateFormat)
print("This Week: \(thisMon) - \(thisSun)")
print("Next Week: \(nextMon) - \(nextSun)")
}
extension Date {
func getWeekDates() -> (thisWeek:[Date],nextWeek:[Date]) {
var tuple: (thisWeek:[Date],nextWeek:[Date])
var arrThisWeek: [Date] = []
for i in 0..<7 {
arrThisWeek.append(Calendar.current.date(byAdding: .day, value: i, to: startOfWeek)!)
}
var arrNextWeek: [Date] = []
for i in 1...7 {
arrNextWeek.append(Calendar.current.date(byAdding: .day, value: i, to: arrThisWeek.last!)!)
}
tuple = (thisWeek: arrThisWeek,nextWeek: arrNextWeek)
return tuple
}
var startOfWeek: Date {
let gregorian = Calendar(identifier: .gregorian)
let sunday = gregorian.date(from: gregorian.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self))
return gregorian.date(byAdding: .day, value: 1, to: sunday!)!
}
func toDate(format: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = format
return formatter.string(from: self)
}
}
I have created a function that takes a starting date and the number of weeks as arguments and returns an array of DateInterval for the week start and end dates
func weeks(from date: Date, duration: Int) -> [DateInterval]? {
let calendar = Calendar.current
//Get date for first day of the week
let diff = calendar.component(.weekday, from: date) - calendar.firstWeekday
guard let firstDate = calendar.date(byAdding: .day, value: -(diff < 0 ? 7 + diff : diff), to: date) else {
return nil
}
//Generate start and end date for the duration
var weeks = [DateInterval(start: firstDate, end: calendar.date(byAdding: .day, value: 6, to: firstDate)!)]
for week in 1..<duration {
guard let firstDayOfWeek = calendar.date(byAdding: .day, value: 7, to: weeks[week - 1].start),
let lastDayOfWeek = calendar.date(byAdding: .day, value: 6, to: firstDayOfWeek) else { return nil }
weeks.append(DateInterval(start: firstDayOfWeek, end: lastDayOfWeek))
}
return weeks
}
and to use it and print the dates you can do
if let weeks = weeks(from: Date(), duration: 5) {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM dd"
for interval in weeks {
print("\(formatter.string(from: interval.start)) - \(formatter.string(from: interval.end))")
}
}
which outputs
July 13 - July 19
July 20 - July 26
July 27 - August 02
August 03 - August 09
August 10 - August 16

Check if specific date isToday (or passed) Swift

via dateformatter, how can I write a function to know if a specific date has passed or is today?
example: March 8, 2020
Date()
if Date() >= 28March2020 {
return true
} else {
return false
}
thanks
You can do:
if Date() >= Calendar.current.dateWith(year: 2020, month: 3, day: 28) ?? Date.distantFuture {
return true
} else {
return false
}
where dateWith(year:month:day:) is defined as:
extension Calendar {
func dateWith(year: Int, month: Int, day: Int) -> Date? {
var dateComponents = DateComponents()
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
return date(from: dateComponents)
}
}
This method basically returns the Date with the specified year, month, and day, with the hour, minute, and second components all being 0, that is, start of the specified day. In other words, I am checking whether the instant now is after the start of the day 2020-03-28.
The easiest way and most flexible is to convert it to seconds and then compare.
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-dd"
let today = Date().timeIntervalSince1970
let date1 = formatter.date(from: "2022-10-01")!.timeIntervalSince1970
let date2 = formatter.date(from: "2022-10-02")!.timeIntervalSince1970
if today >= date1 && today <= date2 {
}

How do I find the beginning of the week from an NSDate?

I'm implementing a calendar view, and I'd like it to start at the beginning of the week containing a particular date. Eg. If the target date is Monday, Feb 29, 2016, and the current calendar is set to start on Sunday, I'd like my view to start with Sunday, February 28.
This seems like it should be straightforward:
let calendar = NSCalendar.currentCalendar()
let firstDate = calendar.nextDateAfterDate(targetDate,
matchingUnit: .Weekday,
value: calendar.firstWeekday,
options: .SearchBackwards)
But this fails with:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Exactly one option from the set {NSCalendarMatchPreviousTimePreservingSmallerUnits, NSCalendarMatchNextTimePreservingSmallerUnits, NSCalendarMatchNextTime} must be specified.'
I can get basically what I want with:
let firstDate = calendar.nextDateAfterDate(firstDate,
matchingUnit: .Weekday,
value: calendar.firstWeekday,
options: .MatchPreviousTimePreservingSmallerUnits)?
.dateByAddingTimeInterval(-7 * 84600)
But it seems like a bad practice, since sometimes the number of seconds in a day isn't 86400.
Is there a better way?
you can use Calendar method date(from: DateComponents) passing [.yearForWeekOfYear, .weekOfYear] components from any date it will return the first day of the week from the calendar used. So if you would like to get Sunday just use Gregorian calendar. If you would like to get the Monday as the first day of the week you can use Calendar .iso8601 as you can see in this answer
Xcode 12 • Swift 5.3 or later (works with previous Swift versions as well)
extension Calendar {
static let gregorian = Calendar(identifier: .gregorian)
}
extension Date {
func startOfWeek(using calendar: Calendar = .gregorian) -> Date {
calendar.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: self).date!
}
}
usage:
Date().startOfWeek() // "Sep 20, 2020 at 12:00 AM"
If you would like to get the beginning of week at a particular timezone you just need to use a custom calendar:
var gregorianUTC = Calendar.gregorian
gregorianUTC.timeZone = TimeZone(identifier: "UTC")!
print(Date().startOfWeek(using: gregorianUTC)) // "2020-09-20 00:00:00 +0000\n"
Swift 4 Solution
I have figured out according to my requirement, where I have find out dates for following.
1. Today
2. Tomorrow
3. This Week
4. This Weekend
5. Next Week
6. Next Weekend
So, I have created Date Extension to get Dates of Current Week and Next Week.
CODE
extension Date {
func getWeekDates() -> (thisWeek:[Date],nextWeek:[Date]) {
var tuple: (thisWeek:[Date],nextWeek:[Date])
var arrThisWeek: [Date] = []
for i in 0..<7 {
arrThisWeek.append(Calendar.current.date(byAdding: .day, value: i, to: startOfWeek)!)
}
var arrNextWeek: [Date] = []
for i in 1...7 {
arrNextWeek.append(Calendar.current.date(byAdding: .day, value: i, to: arrThisWeek.last!)!)
}
tuple = (thisWeek: arrThisWeek,nextWeek: arrNextWeek)
return tuple
}
var tomorrow: Date {
return Calendar.current.date(byAdding: .day, value: 1, to: noon)!
}
var noon: Date {
return Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
}
var startOfWeek: Date {
let gregorian = Calendar(identifier: .gregorian)
let sunday = gregorian.date(from: gregorian.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self))
return gregorian.date(byAdding: .day, value: 1, to: sunday!)!
}
func toDate(format: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = format
return formatter.string(from: self)
}
}
USAGE:
let arrWeekDates = Date().getWeekDates() // Get dates of Current and Next week.
let dateFormat = "MMM dd" // Date format
let thisMon = arrWeekDates.thisWeek.first!.toDate(format: dateFormat)
let thisSat = arrWeekDates.thisWeek[arrWeekDates.thisWeek.count - 2].toDate(format: dateFormat)
let thisSun = arrWeekDates.thisWeek[arrWeekDates.thisWeek.count - 1].toDate(format: dateFormat)
let nextMon = arrWeekDates.nextWeek.first!.toDate(format: dateFormat)
let nextSat = arrWeekDates.nextWeek[arrWeekDates.nextWeek.count - 2].toDate(format: dateFormat)
let nextSun = arrWeekDates.nextWeek[arrWeekDates.nextWeek.count - 1].toDate(format: dateFormat)
print("Today: \(Date().toDate(format: dateFormat))") // Sep 26
print("Tomorrow: \(Date().tomorrow.toDate(format: dateFormat))") // Sep 27
print("This Week: \(thisMon) - \(thisSun)") // Sep 24 - Sep 30
print("This Weekend: \(thisSat) - \(thisSun)") // Sep 29 - Sep 30
print("Next Week: \(nextMon) - \(nextSun)") // Oct 01 - Oct 07
print("Next Weekend: \(nextSat) - \(nextSun)") // Oct 06 - Oct 07
You can modify Extension according to your need.
Thanks!
You can implement this as Date class extension or something. It should returns something like 2020-01-06 00:00:00 +0000
Xcode 11.3 Swift 5
func firstDayOfWeek() -> Date {
var c = Calendar(identifier: .iso8601)
c.timeZone = TimeZone(secondsFromGMT: 0)!
print(
c.date(from: c.dateComponents([.weekOfYear, .yearForWeekOfYear], from: Date()))!
)
}
The Calendar has a mechanism for finding date at the start of a given time interval (say week of year, or month) that contains a given date:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date = dateFormatter.date(from: "2017-01-07")
if let date = date {
let calendar = Calendar(identifier: .gregorian)
var startDate : Date = Date()
var interval : TimeInterval = 0
if calendar.dateInterval(of: .weekOfYear, start: &startDate, interval: &interval, for: date) {
print("Start of week is \(startDate)")
// prints "Start of week is 2017-01-01 06:00:00 +0000"
}
}
In order to get the user's locale settings respected correctly, you should use the user's Calendar firstWeekday property in the DateComponents. This is what I usually use:
// MARK: first day of week
extension Date {
/**
Finds the first day of the week the subject date falls into.
- Parameter calendar: The calendar to use. Defaults to the user's current calendar.
- Returns: The `Date` of the first day of the week into which the subject date falls.
`startOfWeek()` respects the user's locale settings, i.e. will automatically use Sunday/Monday/etc. as first
weekday based on the user's region and locale settings.
*/
func startOfWeek(using calendar: Calendar = .current) -> Date? {
var components = calendar.dateComponents([.weekday, .year, .month, .weekOfYear], from: self)
components.weekday = calendar.firstWeekday
return calendar.date(from: components)
}
}
Basically use
NSCalender
and
dateByAddingComponents
. For solving of you're problem try to use this code sample:
let cal = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.weekOfYear -= 1
if let date = cal.dateByAddingComponents(components, toDate: NSDate(), options: NSCalendarOptions(0)) {
var beginningOfWeek: NSDate?
var weekDuration = NSTimeInterval()
if cal.rangeOfUnit(.CalendarUnitWeekOfYear, startDate: &beginningOfWeek, interval: &weekDuration, forDate: date) {
print(beginningOfWeek)
}
}
I had problems with all previous solutions, since they do not take into account user's calendar setting. Next code will be taking into account that.
extension Date {
var startOfWeek: Date? {
let calendar = Calendar.current
var components: DateComponents? = calendar.dateComponents([.weekday, .year, .month, .day], from: self)
var modifiedComponent = components
modifiedComponent?.day = (components?.day ?? 0) - ((components?.weekday ?? 0) - 1)
return calendar.date(from: modifiedComponent!)
}
var endOfWeek: Date? {
let calendar = Calendar.current
var components: DateComponents? = calendar.dateComponents([.weekday, .year, .month, .day], from: self)
var modifiedComponent = components
modifiedComponent?.day = (components?.day ?? 0) + (7 - (components?.weekday ?? 0))
modifiedComponent?.hour = 23
modifiedComponent?.minute = 59
modifiedComponent?.second = 59
return calendar.date(from: modifiedComponent!)
}
}