I have a UIDatePicker which is available for days from today to the next week ( from Date() to Date().day + 7 ).
Suppose that today is 30th day of the month, so the available days to choose are 30th day of this month and first to 6th day of next month.
In this case, I need to change the datePicker month to next month as the user change day from 30 to 1.
But the problem is:
"When I change the day to an invalid one, the .change method give me the least valid day."
Example:
Assumptions:
Today = 28 July.
Choosable dates = 28 July to 3 August.
What I need:
User can not choose the days before 28 July and not after 3 August.
When user wants to change day to 1, 2 or 3 August, the datePicker itself change the month to August and make 1, 2 and 3 available to
be chosen by user.
Main Problem
When user change the day to 1, 2 or 3, I can not get the 1, 2 or 3! and it returns me "28"!
Just set maximumDate property of your UIDatePicker to desired date.
func setupDatePicker(){
var currDateComponents = getDateComponents(fromDate: Date())
var maxDateComponents = currDateComponents
if let date = currDateComponents?.day{
maxDateComponents?.day = date + 7
}
self.datePicker?.maximumDate = getDate(fromDateComponents: maxDateComponents)
}
func getDateComponents(fromDate date: Date?)-> DateComponents?{
let calendar = Calendar(identifier: .gregorian)
var dateComponents: DateComponents? = nil
if(date != nil){
dateComponents = calendar.dateComponents([.day, .month, .year], from: date!)
}
return dateComponents
}
func getDate(fromDateComponents dateComponents:DateComponents?)-> Date?{
let calendar = Calendar(identifier: .gregorian)
if let componentsBasedDate = calendar.date(from: dateComponents!) {
return componentsBasedDate
}
return nil
}
Call this function in viewDidLoad to setup calendar for setting maximum and minimum date:
fileprivate func setupCalendar() {
datePicker.minimumDate = Calendar.current.date(byAdding: .day, value: 0, to: ServerTime.sharedInstance.nowTime)
datePicker.maximumDate = Calendar.current.date(byAdding: .day, value: +7, to: ServerTime.sharedInstance.nowTime)
}
for calculate days between shown date and current date I've used this method:
extension Date {
//Calculate days between two day objects
func daysBetween(_ date: Date) -> Int {
let calendar = NSCalendar.current
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: date)
let date2 = calendar.startOfDay(for: self)
let components = calendar.dateComponents([.day], from: date1, to: date2)
return components.day ?? 0 // This will return the number of day(s) between dates
}
}}
In fact, it is impossible to get date before they chose. And according to unavailability of days in next month before changing the month, I can not use UIDatePicker to handle it.
So Finally I implemented my custom UIPickerView.
as #holex said.
Thank you all for answering.
Related
I am currently making a scheduling feature for my application. I have a view where a user is able to select what days of the week they will be available for. Now, I am looking for a way to generate an array of dates for the given weekdays for the upcoming month.
In other words, if the user selects that they will be available on Monday, the function needs to return all dates that satisfy the given predicament for the month ahead (4 date objects).
Here's what I have tried but it doesn't seem to work:
var masterSchedule = [WorkDay(weekDay: 1, startTime: 8, endTime: 16, busyHours: []), WorkDay(weekDay: 4, startTime: 8, endTime: 16, busyHours: [])]
func getDates() {
for workDay in masterSchedule {
var components = DateComponents()
components.weekday = workDay.weekDay
components.year = 2021
components.month = 8
let date = Calendar.current.date(from: components) ?? Date()
availableDays.append(date)
}
}
For some reason, the output returns 2 same dates of 2021-07-31 23:00:00 +0000.
Can someone help me?
Here's an example that prints all the Mondays in October
import UIKit
import SwiftUI
import PlaygroundSupport
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.dateStyle = .short
let firstOfOctober = formatter.date(from: "10/1/2021")!
if let interval = calendar.dateInterval(of: .month, for: firstOfOctober) {
let mondays = DateComponents(weekday: 2)
calendar.enumerateDates(startingAfter: firstOfOctober,
matching: mondays, matchingPolicy: .previousTimePreservingSmallerComponents) {
date, exactMatch, stopLooking in
if let date = date {
if date < interval.end {
print(formatter.string(from: date))
} else {
stopLooking = true
}
}
}
}
Hoping for some help on this one. I'm trying to search (forwards and backwards) to find the next date that matches the day of the year
So, for instance I have a day value of 300, I know we are currently on day 237 of this year (as of writing this post, 25/08/18!), and I want to search backwards to find the previous occurrence of the 300th day of a year, and format a date from it.
I'm already extracting the 'day of year' from date using a small Date extension:
extension Date {
var dayOfYear: Int {
return Calendar.current.ordinality(of: .day, in: .year, for: self)!
}
}
Using the Calendar.nextDate() function I can search to match '.day' , but that's obviously day of month, not year:
let potentialSartDate = (Calendar.current as NSCalendar).nextDate(after: nowDate, matching: .day, value: dayOfYearValueToMatch, options: [.matchNextTime, .searchBackwards])!
Does anyone have any pointers, or approach, of how to perform a search like this build a date out of the result?
Thanks in advance!
Emile
You can get the current year component from today and use the Calendar date(from: DateComponents) method to get the 300th day of the year as follow:
extension Calendar {
static let iso8601 = Calendar(identifier: .iso8601)
}
extension Date {
var year: Int {
return Calendar.current.component(.year, from: self)
}
var last300ThDayOfYear: Date {
return Calendar.current.date(from: DateComponents(calendar: .iso8601, year: dayOfYear > 300 ? year : year - 1, day: 300))!
}
var dayOfYear: Int {
return Calendar.current.ordinality(of: .day, in: .year, for: self)!
}
}
let date = Date().last300ThDayOfYear // "Oct 27, 2017 at 12:00 AM"
print(date.dayOfYear) // 300
I creating an itinerary generation app where the user is required to enter the dates of his/her trip. The only problem is, using UIDatePicker the dates are always given as the current time for a given day/month/year.
In a separate file I've extended Date class to try and write a simple method that will return midnight for a given date.
First I tried
var midnight:Date{
let cal = Calendar(identifier: .gregorian)
return cal.startOfDay(for: self)
}
However this always gave me either 04:00 or 05:00 depending on daylights savings, which gave me the idea that I should simply remove 4 or 5 hours depending on daylight savings, and so I created the following methods:
var timezone:TimeZone{
return TimeZone.current
}
///Returns the first instance of the date, e.g. 2018-02-26 00:00:00
var trueMidnight:Date{
let cal = Calendar(identifier: .gregorian)
let midnight = cal.startOfDay(for: self)
let secondsFromGMT = TimeZone.current.secondsFromGMT()
print("Daylight savings? \(daylightSavings)")
return midnight.addingTimeInterval(Double(secondsFromGMT))
}
///If this var returns true, then daylight savings time is active and an hour of daylight is gained (during the summer).
var isDaylightSavings:Bool{
return timezone.daylightSavingTimeOffset(for: self) == 0 ? false : true
}
var daylightSavings:Double{
return isDaylightSavings ? 3600.0 : 0.0
}
However these methods sometimes return midnight, 23:00, or even 22:00 the previous day.
I'm a relatively inexperienced programmer so I feel like I'm lacking a basic understanding for the date class or missing a large concept. Why is it so difficult for me to simply find midnight on a given date?
I even forsook the idea of returning midnight and tried to just find noon on a given day with the code:
var noon:Date{
let gregorian = Calendar(identifier: .gregorian)
var components = gregorian.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self)
components.hour = 12
components.minute = 0
components.second = 0
return gregorian.date(from: components)!
}
But this returns 16:00 or 17:00 as opposed to noon. Any help would be appreciated.
When you print a date, it is printed in UTC time. So when you print your Dates, they differ from your local time by 4/5 hours.
If you use the following code instead
print(yourDate.description(with: .current))
Where yourDate is your date, it will be in the correct time zone.
You're confused.
If you use
print(Date())
You will get a date in UTC. If you're in the UTC+5 time zone, that date will be 5 hours greater than your local time. Thus if you try to display midnight local time in UTC, it will show up as 5:00 AM in UTC.
Try this:
extension Date {
func localString(dateStyle: DateFormatter.Style = .medium,
timeStyle: DateFormatter.Style = .medium) -> String {
return DateFormatter.localizedString(
from: self,
dateStyle: dateStyle,
timeStyle: timeStyle)
}
var midnight:Date{
let cal = Calendar(identifier: .gregorian)
return cal.startOfDay(for: self)
}
}
print("Tonight at midnight is " + Date().midnight.localString())
That code uses a function localString() that takes advantage of a DateFormatter method localizedString(from:dateStyle:timeStyle:) that converts a Date to a string in the current locale (which includes the local time zone.
I suggest adding that extension to your apps.
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!)
}
}
i have a date say 2 March 2016 stored as NSUserDefaults and i want to add a new row in TableView every time a new Month is about to come , so what should i do for accomplishing this , IMO comparing the stored Date and Current Date and if
in Curent Date a new Month is about to come in next 7 days then add the
row into table but i don't know where to start, anyone can give me some hint for checking current date's next 7 days for if a new months is about to come
and if my approach is not good enough then please correct me it'll be so appreciated by me and helpful for me
please see example for better understanding :
storedDate = 2 March 2016
currentDate = 26 March 2016
if CurrentDate + 1 Week == newMonth {
//add the Row into TableView
}
You can add an Extension to NSDate and then do all sorts of day/month addition
This method you can use to add 7 days to the current date...
func dateByAddingDays(daysToAdd: Int)-> NSDate {
let dateComponents = NSDateComponents()
dateComponents.day = daysToAdd
let newDate = NSCalendar.currentCalendar().dateByAddingComponents(dateComponents, toDate: self, options: .MatchFirst)
return newDate!
}
This method to add months to current date
func dateByAddingMonths(monthsToAdd: Int)-> NSDate {
let dateComponents = NSDateComponents()
dateComponents.month = monthsToAdd
let newDate = NSCalendar.currentCalendar().dateByAddingComponents(dateComponents, toDate: self, options: .MatchFirst)
return newDate!
}
Then you need to check that date you created and see if it its a different month than the one that is stored..
func compareMonths(newDate:NSDate)-> Bool {
let today = NSDate()
let todayPlusSeven = today.dateByAddingDays(7)
return todayPlusSeven.isNextMonth(storedDate)
}
Using this method to check if the months of 2 dates are the same
func isNextMonth(storedDate: NSDate)-> Bool {
return isSameMonthAsDate(storedDate.dateByAddingMonth(1))
}
func isSameMonthAsDate(compareDate: NSDate)-> Bool {
let comp1 = NSCalendar.currentCalendar().components([NSCalendarUnit.Year, NSCalendarUnit.Month], fromDate: self)
let comp2 = NSCalendar.currentCalendar().components([NSCalendarUnit.Year, NSCalendarUnit.Month], fromDate: compareDate)
return ((comp1.month == comp2.month) && (comp1.year == comp2.year))
}
An oldie but still goodie, is this page of Date helpers from Erica Sadun's github page here They are all in Obj-c but can be converted to swift easily enough. I still reference it when i need help doing date math