Working out the start and end of a day. Swift - swift

I have a function to work out the start and end of a week which works as expected. I want to implement another function which works out the start and end of a single day. I have the code below however I get the following error:
Type of expression is ambiguous without more context.
public class Date {
let dateFormatter = NSDateFormatter()
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
func calcStartAndEndDateForWeek(durationOccurance: Double) {
print("Calculating start and end for week")
let componentsWeek = calendar.components([.YearForWeekOfYear, .WeekOfYear], fromDate: date)
let startOfWeek = calendar.dateFromComponents(componentsWeek)!
print("start of Week = \(dateFormatter.stringFromDate(startOfWeek))")
let componentsWeekEnds = NSDateComponents()
componentsWeekEnds.weekOfYear = 1
let endOfWeek = calendar.dateByAddingComponents(componentsWeekEnds, toDate: startOfWeek, options: [])!
print("End of the week = \(dateFormatter.stringFromDate(endOfWeek))")
}
func calcStartAndEndDateForDay(durationOccurance: Double) {
print("Calculating start and end for day")
let componentsWeek = calendar.components([.Minutes, .Seconds], fromDate: date)
let startOfDay = calendar.dateFromComponents(componentsWeek)!
print("start day = \(dateFormatter.stringFromDate(startOfDay))")
}
init(){
dateFormatter.dateFormat = "dd-MM-yyyy"
}
}

We can create a more generic function using the methods on NSCalendar:
func rangeOfPeriod(period: NSCalendarUnit, date: NSDate) -> (NSDate, NSDate) {
let calendar = NSCalendar.currentCalendar()
var startDate: NSDate? = nil
// let's ask calendar for the start of the period
calendar.rangeOfUnit(period, startDate: &startDate, interval: nil, forDate: date)
// end of this period is the start of the next period
let endDate = calendar.dateByAddingUnit(period, value: 1, toDate: startDate!, options: [])
// you can subtract 1 second if you want to make "Feb 1 00:00:00" into "Jan 31 23:59:59"
// let endDate2 = calendar.dateByAddingUnit(.Second, value: -1, toDate: endDate!, options: [])
return (startDate!, endDate!)
}
Called as
print("\(rangeOfPeriod(.WeekOfYear, date: NSDate()))")
print("\(rangeOfPeriod(.Day, date: NSDate()))")
Putting it into your code:
public class Date {
let dateFormatter = NSDateFormatter()
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
func rangeOfPeriod(period: NSCalendarUnit) -> (NSDate, NSDate) {
var startDate: NSDate? = nil
calendar.rangeOfUnit(period, startDate: &startDate, interval: nil, forDate: date)
let endDate = calendar.dateByAddingUnit(period, value: 1, toDate: startDate!, options: [])
return (startDate!, endDate!)
}
func calcStartAndEndDateForWeek() {
let (startOfWeek, endOfWeek) = rangeOfPeriod(.WeekOfYear)
print("Start of week = \(dateFormatter.stringFromDate(startOfWeek))")
print("End of the week = \(dateFormatter.stringFromDate(endOfWeek))")
}
func calcStartAndEndDateForDay() {
let (startOfDay, endOfDay) = rangeOfPeriod(.Day)
print("Start of day = \(dateFormatter.stringFromDate(startOfDay))")
print("End of the day = \(dateFormatter.stringFromDate(endOfDay))")
}
init() {
dateFormatter.dateFormat = "dd-MM-yyyy"
}
}
let myDate = Date()
myDate.calcStartAndEndDateForWeek()
myDate.calcStartAndEndDateForDay()

I was implementing something similar and went the following route:
extension Date {
static var startOfToday: Date? {
let date = Date()
guard !date.isStartOfDay else { return date }
return date
.zero(out: .second)?
.zero(out: .minute)?
.zero(out: .hour)?
.addingTimeInterval(-24 * 60 * 60)
}
private func zero(out: Calendar.Component) -> Date? {
return Calendar.current
.date(bySetting: out, value: 0, of: self)
}
private var isStartOfDay: Bool {
let cal = Calendar.current
let hours = cal.component(.hour, from: self)
let minutes = cal.component(.minute, from: self)
let seconds = cal.component(.second, from: self)
return hours == 0 && minutes == 0 && seconds == 0
}
}
Setting a component to zero will increment the next bigger component. So just setting the hour to zero will push the date to the next day at 00:00, unless of course the hour is already at zero. So to make it work for any date we have to zero out the seconds, minutes and hours (in that order). And to make sure we don't end up at the beginning of yesterday we first check if all values aren't already at zero.
I realize this is kinda hacky and probably not the best way to go about this, but it seems to work well enough for my use-case at least.
Getting the end of the day can be built on top of this by just adding another day.

Related

RelativeDateTimeFormatter returns one day less than expected [duplicate]

I'm wondering if there is some new and awesome possibility to get the amount of days between two NSDates in Swift / the "new" Cocoa?
E.g. like in Ruby I would do:
(end_date - start_date).to_i
You have to consider the time difference as well. For example if you compare the dates 2015-01-01 10:00 and 2015-01-02 09:00, days between those dates will return as 0 (zero) since the difference between those dates is less than 24 hours (it's 23 hours).
If your purpose is to get the exact day number between two dates, you can work around this issue like this:
// Assuming that firstDate and secondDate are defined
// ...
let calendar = NSCalendar.currentCalendar()
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDayForDate(firstDate)
let date2 = calendar.startOfDayForDate(secondDate)
let flags = NSCalendarUnit.Day
let components = calendar.components(flags, fromDate: date1, toDate: date2, options: [])
components.day // This will return the number of day(s) between dates
Swift 3 and Swift 4 Version
let calendar = Calendar.current
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: firstDate)
let date2 = calendar.startOfDay(for: secondDate)
let components = calendar.dateComponents([.day], from: date1, to: date2)
Here is my answer for Swift 2:
func daysBetweenDates(startDate: NSDate, endDate: NSDate) -> Int
{
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day], fromDate: startDate, toDate: endDate, options: [])
return components.day
}
I see a couple Swift3 answers so I'll add my own:
public static func daysBetween(start: Date, end: Date) -> Int {
Calendar.current.dateComponents([.day], from: start, to: end).day!
}
The naming feels more Swifty, it's one line, and using the latest dateComponents() method.
Here is very nice, Date extension to get difference between dates in years, months, days, hours, minutes, seconds
extension Date {
func years(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.year], from: sinceDate, to: self).year
}
func months(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.month], from: sinceDate, to: self).month
}
func days(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.day], from: sinceDate, to: self).day
}
func hours(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.hour], from: sinceDate, to: self).hour
}
func minutes(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.minute], from: sinceDate, to: self).minute
}
func seconds(sinceDate: Date) -> Int? {
return Calendar.current.dateComponents([.second], from: sinceDate, to: self).second
}
}
I translated my Objective-C answer
let start = "2010-09-01"
let end = "2010-09-05"
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let startDate:NSDate = dateFormatter.dateFromString(start)
let endDate:NSDate = dateFormatter.dateFromString(end)
let cal = NSCalendar.currentCalendar()
let unit:NSCalendarUnit = .Day
let components = cal.components(unit, fromDate: startDate, toDate: endDate, options: nil)
println(components)
result
<NSDateComponents: 0x10280a8a0>
Day: 4
The hardest part was that the autocompletion insists fromDate and toDate would be NSDate?, but indeed they must be NSDate! as shown in the reference.
I don't see how a good solution with an operator would look like, as you want to specify the unit differently in each case. You could return the time interval, but than won't you gain much.
Update for Swift 3 iOS 10 Beta 4
func daysBetweenDates(startDate: Date, endDate: Date) -> Int {
let calendar = Calendar.current
let components = calendar.dateComponents([Calendar.Component.day], from: startDate, to: endDate)
return components.day!
}
Swift 5. Thanks to Emin Buğra Saral above for the startOfDay suggestion.
extension Date {
func daysBetween(date: Date) -> Int {
return Date.daysBetween(start: self, end: date)
}
static func daysBetween(start: Date, end: Date) -> Int {
let calendar = Calendar.current
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: start)
let date2 = calendar.startOfDay(for: end)
let a = calendar.dateComponents([.day], from: date1, to: date2)
return a.value(for: .day)!
}
}
Usage:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let start = dateFormatter.date(from: "2017-01-01")!
let end = dateFormatter.date(from: "2018-01-01")!
let diff = Date.daysBetween(start: start, end: end) // 365
// or
let diff = start.daysBetween(date: end) // 365
Here is the answer for Swift 3 (tested for IOS 10 Beta)
func daysBetweenDates(startDate: Date, endDate: Date) -> Int
{
let calendar = Calendar.current
let components = calendar.components([.day], from: startDate, to: endDate, options: [])
return components.day!
}
Then you can call it like this
let pickedDate: Date = sender.date
let NumOfDays: Int = daysBetweenDates(startDate: pickedDate, endDate: Date())
print("Num of Days: \(NumOfDays)")
Swift 5
Working, you need to set the time to be the same for both days, if you are off by seconds it will be wrong
func daysBetween(start: Date, end: Date) -> Int {
let start = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: start)!
let end = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: end)!
return Calendar.current.dateComponents([.day], from: start, to: end).day ?? 0
}
The things built into swift are still very basic. As they should be at this early stage. But you can add your own stuff with the risk that comes with overloading operators and global domain functions. They will be local to your module though.
let now = NSDate()
let seventies = NSDate(timeIntervalSince1970: 0)
// Standard solution still works
let days = NSCalendar.currentCalendar().components(.CalendarUnitDay,
fromDate: seventies, toDate: now, options: nil).day
// Flashy swift... maybe...
func -(lhs:NSDate, rhs:NSDate) -> DateRange {
return DateRange(startDate: rhs, endDate: lhs)
}
class DateRange {
let startDate:NSDate
let endDate:NSDate
var calendar = NSCalendar.currentCalendar()
var days: Int {
return calendar.components(.CalendarUnitDay,
fromDate: startDate, toDate: endDate, options: nil).day
}
var months: Int {
return calendar.components(.CalendarUnitMonth,
fromDate: startDate, toDate: endDate, options: nil).month
}
init(startDate:NSDate, endDate:NSDate) {
self.startDate = startDate
self.endDate = endDate
}
}
// Now you can do this...
(now - seventies).months
(now - seventies).days
This is an updated version of Emin's answer for Swift 5 that incorporates the suggestion to use noon instead of midnight as the definitive time for comparing days. It also handles the potential failure of various date functions by returning an optional.
///
/// This is an approximation; it does not account for time differences. It will set the time to 1200 (noon) and provide the absolute number
/// of days between now and the given date. If the result is negative, it should be read as "days ago" instead of "days from today."
/// Returns nil if something goes wrong initializing or adjusting dates.
///
func daysFromToday() -> Int?
{
let calendar = NSCalendar.current
// Replace the hour (time) of both dates with noon. (Noon is less likely to be affected by DST changes, timezones, etc. than midnight.)
guard let date1 = calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: Date())),
let date2 = calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: self)) else
{
return nil
}
return calendar.dateComponents([.day], from: date1, to: date2).day
}
Here is my answer for Swift 3:
func daysBetweenDates(startDate: NSDate, endDate: NSDate, inTimeZone timeZone: TimeZone? = nil) -> Int {
var calendar = Calendar.current
if let timeZone = timeZone {
calendar.timeZone = timeZone
}
let dateComponents = calendar.dateComponents([.day], from: startDate.startOfDay, to: endDate.startOfDay)
return dateComponents.day!
}
You could use the following extension:
public extension Date {
func daysTo(_ date: Date) -> Int? {
let calendar = Calendar.current
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: self)
let date2 = calendar.startOfDay(for: date)
let components = calendar.dateComponents([.day], from: date1, to: date2)
return components.day // This will return the number of day(s) between dates
}
}
Then, you can call it like this:
startDate.daysTo(endDate)
There's hardly any Swift-specific standard library yet; just the lean basic numeric, string, and collection types.
It's perfectly possible to define such shorthands using extensions, but as far as the actual out-of-the-box APIs goes, there is no "new" Cocoa; Swift just maps directly to the same old verbose Cocoa APIs as they already exist.
I'm going to add my version even though this thread is a year old. My code looks like this:
var name = txtName.stringValue // Get the users name
// Get the date components from the window controls
var dateComponents = NSDateComponents()
dateComponents.day = txtDOBDay.integerValue
dateComponents.month = txtDOBMonth.integerValue
dateComponents.year = txtDOBYear.integerValue
// Make a Gregorian calendar
let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)
// Get the two dates we need
var birthdate = calendar?.dateFromComponents(dateComponents)
let currentDate = NSDate()
var durationDateComponents = calendar?.components(NSCalendarUnit.CalendarUnitDay, fromDate: birthdate!, toDate: currentDate, options: nil)
let numberOfDaysAlive = durationDateComponents?.day
println("\(numberOfDaysAlive!)")
txtGreeting.stringValue = "Hello \(name), You have been alive for \(numberOfDaysAlive!) days."
I hope it helps someone.
Cheers,
Erin's method updated to Swift 3, This shows days from today (disregarding time of day)
func daysBetweenDates( endDate: Date) -> Int
let calendar: Calendar = Calendar.current
let date1 = calendar.startOfDay(for: Date())
let date2 = calendar.startOfDay(for: secondDate)
return calendar.dateComponents([.day], from: date1, to: date2).day!
}
This returns an absolute difference in days between some Date and today:
extension Date {
func daysFromToday() -> Int {
return abs(Calendar.current.dateComponents([.day], from: self, to: Date()).day!)
}
}
and then use it:
if someDate.daysFromToday() >= 7 {
// at least a week from today
}
easier option would be to create a extension on Date
public extension Date {
public var currentCalendar: Calendar {
return Calendar.autoupdatingCurrent
}
public func daysBetween(_ date: Date) -> Int {
let components = currentCalendar.dateComponents([.day], from: self, to: date)
return components.day!
}
}
Swift 3.2
extension DateComponentsFormatter {
func difference(from fromDate: Date, to toDate: Date) -> String? {
self.allowedUnits = [.year,.month,.weekOfMonth,.day]
self.maximumUnitCount = 1
self.unitsStyle = .full
return self.string(from: fromDate, to: toDate)
}
}
All answer is good. But for Localizations we need calculates a number of decimal days in between two dates. so we can provide the sustainable decimal format.
// This method returns the fractional number of days between to dates
func getFractionalDaysBetweenDates(date1: Date, date2: Date) -> Double {
let components = Calendar.current.dateComponents([.day, .hour], from: date1, to: date2)
var decimalDays = Double(components.day!)
decimalDays += Double(components.hour!) / 24.0
return decimalDays
}
Nice handy one liner :
extension Date {
var daysFromNow: Int {
return Calendar.current.dateComponents([.day], from: Date(), to: self).day!
}
}
Swift 3 - Days from today until date
func daysUntilDate(endDateComponents: DateComponents) -> Int
{
let cal = Calendar.current
var components = cal.dateComponents([.era, .year, .month, .day], from: NSDate() as Date)
let today = cal.date(from: components)
let otherDate = cal.date(from: endDateComponents)
components = cal.dateComponents([Calendar.Component.day], from: (today! as Date), to: otherDate!)
return components.day!
}
Call function like this
// Days from today until date
var examnDate = DateComponents()
examnDate.year = 2016
examnDate.month = 12
examnDate.day = 15
let daysCount = daysUntilDate(endDateComponents: examnDate)
extension Date {
func daysFromToday() -> Int {
return Calendar.current.dateComponents([.day], from: self, to: Date()).day!
}
}
Then use it like
func dayCount(dateString: String) -> String{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM dd,yyyy hh:mm a"
let fetchedDate = dateFormatter.date(from: dateString)
let day = fetchedDate?.daysFromToday()
if day! > -1{
return "\(day!) days passed."
}else{
return "\(day! * -1) days left."
}
}
extension Date {
static func - (recent: Date, previous: Date) -> DateComponents {
var dateComponents = DateComponents()
dateComponents.year = Calendar.current.dateComponents([.day], from: previous, to: recent).year
dateComponents.month = Calendar.current.dateComponents([.month], from: previous, to: recent).month
dateComponents.day = Calendar.current.dateComponents([.day], from: previous, to: recent).day
dateComponents.hour = Calendar.current.dateComponents([.hour], from: previous, to: recent).hour
dateComponents.minute = Calendar.current.dateComponents([.minute], from: previous, to: recent).minute
dateComponents.second = Calendar.current.dateComponents([.second], from: previous, to: recent).second
return dateComponents
}
}
func completeOffset(from date:Date) -> String? {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .brief
return formatter.string(from: Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date, to: self))
}
if you need year month days and hours as string use this
var tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date())!
let dc = tomorrow.completeOffset(from: Date())
2017 version, copy and paste
func simpleIndex(ofDate: Date) -> Int {
// index here just means today 0, yesterday -1, tomorrow 1 etc.
let c = Calendar.current
let todayRightNow = Date()
let d = c.date(bySetting: .hour, value: 13, of: ofDate)
let t = c.date(bySetting: .hour, value: 13, of: todayRightNow)
if d == nil || today == nil {
print("weird problem simpleIndex#ofDate")
return 0
}
let r = c.dateComponents([.day], from: today!, to: d!)
// yesterday is negative one, tomorrow is one
if let o = r.value(for: .day) {
return o
}
else {
print("another weird problem simpleIndex#ofDate")
return 0
}
}
let calendar = NSCalendar.currentCalendar();
let component1 = calendar.component(.Day, fromDate: fromDate)
let component2 = calendar.component(.Day, fromDate: toDate)
let difference = component1 - component2
Swift 5.2.4 solution:
import UIKit
let calendar = Calendar.current
let start = "2010-09-01"
let end = "2010-09-05"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let firstDate = dateFormatter.date(from: start)!
let secondDate = dateFormatter.date(from: end)!
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: firstDate)
let date2 = calendar.startOfDay(for: secondDate)
let components = calendar.dateComponents([Calendar.Component.day], from: date1, to: date2)
components.day // This will return the number of day(s) between dates

Strideable Date

I'm looking for cool ways to stride through Date ranges with different increments (either seconds aka TimeInterval or with DateComponents aka .hour, .minute)
import Foundation
extension Date: Strideable {
// typealias Stride = SignedInteger // doesn't work (probably because declared in extension
public func advanced(by n: Int) -> Date {
self.addingTimeInterval(TimeInterval(n))
}
public func distance(to other: Date) -> Int {
return Int(self.distance(to: other))
}
}
let now = Date()
let dayAfterNow = Date().addingTimeInterval(86400)
let dateRange = now ... dayAfterNow
let dateArr : [Date] = Array(stride(from: now, to: dayAfterNow, by: 60)) // Solves my task but not exactly how I wanted.
let formatter: DateFormatter = {
let df = DateFormatter()
df.timeStyle = .short
return df }()
print (dateArr.prefix(7).map { formatter.string(from: $0) })
/// This hoever doesn't work
// There must be a way to make it work but couldn't figure it out for now
let errDateArr: [Date] = Array(dateRange)
// Error: Initializer 'init(_:)' requires that 'Date.Stride' (aka 'Double') conform to 'SignedInteger'
The second part of the question is that also i'd like to have something like:
var components = DateComponents()
components.hour = 8
components.minute = 0
let date = Calendar.current.date(from: components)
let dateByComponentArr : [Date] = Array(stride(from: now, to: dayAfterNow, by: components))
As already mentionerd by #Alexander you shouldn't try to conform Date to Strideable but you can implement your own methods to generate the next n days, hours or minutes:
extension Date {
func year(using calendar: Calendar = .current) -> Int { calendar.component(.year, from: self) }
func month(using calendar: Calendar = .current) -> Int { calendar.component(.month, from: self) }
func day(using calendar: Calendar = .current) -> Int { calendar.component(.day, from: self) }
func hour(using calendar: Calendar = .current) -> Int { calendar.component(.hour, from: self) }
func minute(using calendar: Calendar = .current) -> Int { calendar.component(.minute, from: self) }
func nextDays(n: Int, nth: Int = 1, using calendar: Calendar = .current) -> [Date] {
let year = self.year(using: calendar)
let month = self.month(using: calendar)
let day = self.day(using: calendar)
var days: [Date] = []
for x in 0..<n where x.isMultiple(of: nth) {
days.append(DateComponents(calendar: calendar, year: year, month: month, day: day + x, hour: 12).date!)
}
return days
}
func nextHours(n: Int, nth: Int = 1, using calendar: Calendar = .current) -> [Date] {
let year = self.year(using: calendar)
let month = self.month(using: calendar)
let day = self.day(using: calendar)
let hour = self.hour(using: calendar)
var hours: [Date] = []
for x in 0..<n where x.isMultiple(of: nth) {
hours.append(DateComponents(calendar: calendar, year: year, month: month, day: day, hour: hour + x).date!)
}
return hours
}
func nextMinutes(n: Int, nth: Int = 1, using calendar: Calendar = .current) -> [Date] {
let year = self.year(using: calendar)
let month = self.month(using: calendar)
let day = self.day(using: calendar)
let hour = self.hour(using: calendar)
let minute = self.minute(using: calendar)
var minutes: [Date] = []
for x in 0..<n where x.isMultiple(of: nth) {
minutes.append(DateComponents(calendar: calendar, year: year, month: month, day: day, hour: hour, minute: minute + x).date!)
}
return minutes
}
}
extension Date {
static let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
return formatter
}()
}
Playgournd testing:
let days = Date().nextDays(n: 10)
for day in days {
print(Date.formatter.string(from: day))
}
let hours = Date().nextHours(n: 10)
for hour in hours {
print(Date.formatter.string(from: hour))
}
let minutes = Date().nextMinutes(n: 10)
for minute in minutes {
print(Date.formatter.string(from: minute))
}
There's a conflicting implementation of distance(to:) already declared on Date: Date.distance(to:). The implementation you defined has the same name, but a different type. This other overload could work, but unfortunately for you, Date already declares a typealias called Stride, which it sets to TimeInterval (just an alias for Double).
I think conforming to Date to Strideable is a bad idea. Take Double for example, which intentionally doesn't conform to Strideable: there's no clear universally-best stride definition. Should it go up by 0.1? 0.01? 3.14? It's not obvious. stride(from:to:by:) exists precisely for this reason, for you to be explicit by how much you're striding by.
let errDateArr: [Date] = Array(now ... dayAfterNow) would definitely score high "WTFs/m" points
For the second part of the question I created a simple sequence that takes daylight saving settings and other stuff in account. It might be helpful in some specific cases.
let dayAfterNow = Date().addingTimeInterval(86400)
var components = DateComponents()
components.hour = 8
components.minute = 0
func dateIterator(start: Date = Date(), by components: DateComponents, wrappingComponents: Bool = false) -> AnyIterator<Date> {
var state = start
return AnyIterator {
let nextDate = Calendar.current.date(byAdding: components, to: state, wrappingComponents: wrappingComponents)
state = nextDate ?? state
return state
}
}
let dateCompSequence = AnySequence(dateIterator(by: components))
let dateArray = Array(dateCompSequence.prefix(10))
dateArray.map{print($0.description)}
print("starting for loop...")
for d in dateCompSequence.prefix(10) {
print(d.description)
}
It is worth noting that Calendar.current.enumerateDates() will likely always help with similar tasks. But having a sequence is sometimes preferable. In earlier version of Swift there seemed to be a strideable DateRange type provided by NSCalendar (just the thing I wanted), but now there is nothing like it in standard library.

swift 4.2 how to correctly check time elapsed

I need to check a date before downloading / manipulate some data from a server. Let's say I need to do that only if 24 hours or more are gone by. this code seems to work, but I'm not sure about it, no way to do it with less lines of code? it seems to be too long to me. i checked this but solutions are quite different from mine.
import UIKit
//standard day formatter
let dateFormatter = DateFormatter()
//let's say this is the date I saved last time i updated data from online server:
let previousDate: String = "2019-03-19 06:40 PM"
dateFormatter.dateFormat = "yyyy-MM-dd hh:mm a"
let old = dateFormatter.date(from: previousDate)
//today I try to download data
let today = Date()
//this simply tests if "moment before" is greater than "moment after"
if today > old! {
print("update data")
} else {
print("do not update")
}
//here I create a measure
let minute:TimeInterval = 60.0
let hour:TimeInterval = 60.0 * minute
let day:TimeInterval = 24 * hour
//here I measure if the old date added of 24h is greater than now, in that case a day or more is passed and I can update
let theOldDateMore24h = Date(timeInterval: day, since: old!)
if theOldDateMore24h < today {
print("passed more than a day: Update!")
} else {
print("less than a day, do not update")
}
There is a method in Calendar
func dateComponents(_ components: Set<Calendar.Component>, from start: Date, to end: Date) -> DateComponents
Get the day component and check greater than 0
let dateFormatter = DateFormatter()
let previousDate = "2019-03-19 06:40 PM"
dateFormatter.dateFormat = "yyyy-MM-dd hh:mm a"
let old = dateFormatter.date(from: previousDate)
//today I try to download data
let today = Date()
if let validDate = old, Calendar.current.dateComponents([.day], from: validDate, to: today).day! > 0 {
print("passed more than a day: Update!")
} else {
print("less than a day, do not update")
}
Quick extension function to simplify it:
extension Date {
func isWithin(_ distanceTime: TimeInterval, after laterDate: Date) -> Bool{
let distance = timeIntervalSince(laterDate)
let result = distanceTime >= distance
return result
}
}
//Usage
let secondsInDay = TimeInterval(60 * 60 * 24)
let isUpToDate = Date().isWithin(secondsInDay, after: previousDate)
if !isUpToDate {
print("passed more than a day: Update!")
}
else {
print("less than a day, do not update")
}
You can actually use an extension for this. It will return the required calendar component
Extension
extension Date {
func interval(ofComponent comp: Calendar.Component, fromDate date: Date) -> Int {
let currentCalendar = Calendar.current
guard let start = currentCalendar.ordinality(of: comp, in: .era, for: date) else { return 0 }
guard let end = currentCalendar.ordinality(of: comp, in: .era, for: self) else { return 0 }
return end - start
}
}
Usage
let yesterday = Date(timeInterval: -86400, since: Date())
let tomorrow = Date(timeInterval: 86400, since: Date())
// Change the component to your preference
let difference = tomorrow.interval(ofComponent: .day, fromDate: yesterday) // returns 2

How can I figure out the day difference in the following example

I am calculating the day difference between two dates, but I figured out the the following code is actually giving me the difference for 24 hours rather than the difference in date. I have the following code:
func daysBetweenDate(startDate: NSDate, endDate: NSDate) -> Int
{
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day], fromDate:startDate, toDate: endDate, options: [])
return components.day
}
So, for the following example I get this result:
lastLaunch:2016-06-10 01:39:07 +0000
toady: 2016-06-11 00:41:41 +0000
dayDiff:0
I would have expected the day difference to be one, since last launch was on the 10th and today is the 11th. How can I change the code to give me the actual difference in date for days?
You can use Calendar method date BySettingHour to get the noon time for your startDate and endDate, to make your calendar calculations not time sensitive:
Xcode 8.2.1 • Swift 3.0.2
extension Date {
var noon: Date? {
return Calendar.autoupdatingCurrent.date(bySettingHour: 12, minute: 0, second: 0, of: self)
}
func daysBetween(_ date: Date) -> Int? {
guard let noon = noon, let date = date.noon else { return nil }
return Calendar.autoupdatingCurrent.dateComponents([.day], from: noon, to: date).day
}
}
let startDateString = "2016-06-10 01:39:07 +0000"
let todayString = "2016-06-11 00:41:41 +0000"
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "ex_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss xxxx"
if let startDate = formatter.date(from: startDateString),
let endDate = formatter.date(from: todayString),
let days = startDate.daysBetween(endDate) {
print(startDate) // "2016-06-10 01:39:07 +0000\n"
print(endDate) // "2016-06-11 00:41:41 +0000\n"
print(days ?? "nil") // 1
}
Swift 2.x
extension NSDate {
var noon: NSDate {
return NSCalendar.currentCalendar().dateBySettingHour(12, minute: 0, second: 0, ofDate: self, options: [])!
}
}
func daysBetweenDate(startDate: NSDate, endDate: NSDate) -> Int {
return NSCalendar.currentCalendar().components([.Day],
fromDate: startDate.noon,
toDate: endDate.noon,
options: []).day
}

Calculate number of weeks in a given month - Swift

Having looked at a few different suggestions on SO, I've not been able to determine why the function below does not work. It seems to return 6 for some months and 5 for others. When changing weeks for days it works perfectly.
For example, trying
weeksInMonth(8, forYear 2015)
Results in 6.
I believe I have a mis-understanding of what 'firstWeekday' property on calendar does but haven't found an adequate explanation by Apple or online.
Have tried both .WeekOfMonth and .WeekOfYear. Again can't find explanation of exact difference.
Any suggestions would be greatly welcome.
func weeksInMonth(month: Int, forYear year: Int) -> Int?
{
if (month < 1 || month > 12) { return nil }
let dateString = String(format: "%4d/%d/01", year, month)
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy/MM/dd"
if let date = dateFormatter.dateFromString(dateString),
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
{
calendar.firstWeekday = 2 // Monday
let weekRange = calendar.rangeOfUnit(.WeekOfMonth, inUnit: .Month, forDate: date)
let weeksCount = weekRange.length
return weeksCount
}
else
{
return nil
}
}
Update:
Apologies my question was not clear. I'm trying to work out how many weeks there are in a month that include a Monday in them. For August this should be 5.
Your code computes the number of weeks which occur (complete or partially)
in a month. What you apparently want is the number of Mondays
in the given month. With NSDateComponents and in particular with
the weekdayOrdinal property you can compute the first
(weekdayOrdinal=1) and last (weekdayOrdinal=-1) Monday
in a month. Then compute the difference in weeks (and add one).
A possible implementation in Swift 2:
func numberOfMondaysInMonth(month: Int, forYear year: Int) -> Int?
{
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
calendar.firstWeekday = 2 // 2 == Monday
// First monday in month:
let comps = NSDateComponents()
comps.month = month
comps.year = year
comps.weekday = calendar.firstWeekday
comps.weekdayOrdinal = 1
guard let first = calendar.dateFromComponents(comps) else {
return nil
}
// Last monday in month:
comps.weekdayOrdinal = -1
guard let last = calendar.dateFromComponents(comps) else {
return nil
}
// Difference in weeks:
let weeks = calendar.components(.WeekOfMonth, fromDate: first, toDate: last, options: [])
return weeks.weekOfMonth + 1
}
Note: That a negative weekdayOrdinal counts backwards from the end of the month is not apparent form the documentation. It was observed in
Determine NSDate for Memorial Day in a given year and confirmed by Dave DeLong).
Update for Swift 3:
func numberOfMondaysInMonth(_ month: Int, forYear year: Int) -> Int? {
var calendar = Calendar(identifier: .gregorian)
calendar.firstWeekday = 2 // 2 == Monday
// First monday in month:
var comps = DateComponents(year: year, month: month,
weekday: calendar.firstWeekday, weekdayOrdinal: 1)
guard let first = calendar.date(from: comps) else {
return nil
}
// Last monday in month:
comps.weekdayOrdinal = -1
guard let last = calendar.date(from: comps) else {
return nil
}
// Difference in weeks:
let weeks = calendar.dateComponents([.weekOfMonth], from: first, to: last)
return weeks.weekOfMonth! + 1
}
Actually your question is : How many Mondays are in a given month?
My approach is to calculate the first Monday for the month, this can be accomplished by setting the CalendarUnit WeekdayOrdinal to 1. Then get the number of total days and do some math.
Swift 1.2
func mondaysInMonth(month: Int, forYear year: Int) -> Int?
{
if 1...12 ~= month {
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let components = NSDateComponents()
components.weekday = 2 // Monday
components.weekdayOrdinal = 1
components.month = month
components.year = year
if let date = calendar.dateFromComponents(components) {
let firstDay = calendar.component(.CalendarUnitDay, fromDate: date)
let days = calendar.rangeOfUnit(.CalendarUnitDay, inUnit:.CalendarUnitMonth, forDate:date).length
return (days - firstDay) / 7 + 1
}
}
return nil
}
Swift 2
func mondaysInMonth(month: Int, forYear year: Int) -> Int?
{
guard 1...12 ~= month else { return nil }
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let components = NSDateComponents()
components.weekday = 2 // Monday
components.weekdayOrdinal = 1
components.month = month
components.year = year
if let date = calendar.dateFromComponents(components) {
let firstDay = calendar.component(.Day, fromDate: date)
let days = calendar.rangeOfUnit(.Day, inUnit:.Month, forDate:date).length
return (days - firstDay) / 7 + 1
}
return nil
}
Swift 2
The following code calculates the number of weeks in a month. It does not depend on what day the month started or ended.
func weeksInMonth(month: Int, year: Int) -> (Int)? {
let calendar = NSCalendar.currentCalendar()
let comps = NSDateComponents()
comps.month = month+1
comps.year = year
comps.day = 0
guard let last = calendar.dateFromComponents(comps) else {
return nil
}
// Note: Could get other options as well
let tag = calendar.components([.WeekOfMonth,.WeekOfYear,
.YearForWeekOfYear,.Weekday,.Quarter],fromDate: last)
return tag.weekOfMonth
}
// Example Usage:
if let numWeeks = weeksInMonth(8,year: 2015) {
print(numWeeks) // Prints 6
}
if let numWeeks = weeksInMonth(12,year: 2015) {
print(numWeeks) // Prints 5
}
Swift 4+:
//-- Get number of weeks from calendar
func numberOfWeeksInMonth(_ date: Date) -> Int {
var calendar = Calendar(identifier: .gregorian)
calendar.firstWeekday = 1
let weekRange = calendar.range(of: .weekOfMonth,
in: .month,
for: date)
return weekRange!.count
}
//-- Implementation
let weekCount = numberOfWeeksInMonth(Date)
Swift 4+:
Generate Date from your components:
let dateComponents = DateComponents.init(year: 2019, month: 5)
let monthCurrentDayFirst = Calendar.current.date(from: dateComponents)!
let monthNextDayFirst = Calendar.current.date(byAdding: .month, value: 1, to: monthCurrentDayFirst)!
let monthCurrentDayLast = Calendar.current.date(byAdding: .day, value: -1, to: monthNextDayFirst)!
Detect week number from date:
let numberOfWeeks = Calendar.current.component(.weekOfMonth, from: monthCurrentDayLast)