Creating a date with DateComponents - swift

I want to get the first day and the last day of the week. But my results do not match the documentation from apple:
https://developer.apple.com/documentation/foundation/nsdatecomponents/1410442-weekday
This is my function:
func startAndEndDateOfWeek(weekOfYearWithYear: (week: Int,year: Int)) -> (start: Date, end: Date) {
var output = (start: Date.init(), end: Date.init())
let calendar = Calendar(identifier: .gregorian)
var firstDayComponents = DateComponents()
firstDayComponents.weekOfYear = weekOfYearWithYear.week
firstDayComponents.yearForWeekOfYear = weekOfYearWithYear.year
firstDayComponents.weekday = 1
let firstDay = calendar.date(from: firstDayComponents)
var lastDayComponents = DateComponents()
lastDayComponents.weekOfYear = weekOfYearWithYear.week
lastDayComponents.yearForWeekOfYear = weekOfYearWithYear.year
lastDayComponents.weekday = 2
let lastDay = calendar.date(from: lastDayComponents)
output = (start: firstDay!, end: lastDay!)
return output
}
.weekday = 2 -> leads to the sunday and not 0.
I also want to have the entire day and not 16:00.

A couple of observations:
In the Gregorian calendar, weekday = 1 means Sunday; weekday = 2 means Monday; etc. You can look at calendar.maximumRange(of: .weekday) to get the range of valid values, and you can look at calendar.weekdaySymbols to see what these weekDay values mean (e.g. “Sun”, “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, and “Sat”).
You said:
I also want to have the entire day and not 16:00.
A Date object references a moment in time. So it can’t represent an “entire day”. But it can represent midnight (and midnight in your time zone is likely 4pm in GMT/UTC/Zulu).
You can, alternatively, return a DateInterval, which does represent a range of time.
func interval(ofWeek week: Int, in year: Int) -> DateInterval {
let calendar = Calendar.current
let date = DateComponents(calendar: calendar, weekOfYear: week, yearForWeekOfYear: year).date!
return calendar.dateInterval(of: .weekOfYear, for: date)!
}
And then
let formatter = DateIntervalFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short
let year = Calendar.current.component(.year, from: Date())
let dateInterval = interval(ofWeek: 2, in: year)
print(formatter.string(from: dateInterval))
In a US locale, the interval starts on January 6th:
1/6/19, 12:00 AM – 1/13/19, 12:00 AM
Whereas in a German locale, the interval starts on the 7th:
07.01.19, 00:00 – 14.01.19, 00:00
If you want the start of the first day of the week and the last day of the week, you can do:
func startAndEndDate(ofWeek week: Int, in year: Int) -> (Date, Date) {
let date = DateComponents(calendar: calendar, weekOfYear: week, yearForWeekOfYear: year).date!
let lastDate = calendar.date(byAdding: .day, value: 6, to: date)!
return (date, lastDate)
}
And then
let formatter = DateFormatter()
formatter.dateStyle = .short
let year = Calendar.current.component(.year, from: Date())
let (start, end) = startAndEndDate(ofWeek: 2, in: year)
print(formatter.string(from: start), "-", formatter.string(from: end))

Related

Swift - Problem converting year + weekOfYear components to Date [duplicate]

When I run this code:
let calendar = Calendar.current
var dateComponents = DateComponents()
dateComponents.weekday = calendar.firstWeekday
dateComponents.weekOfYear = 2
dateComponents.year = 2017
let startOfWeek = calendar.date(from: dateComponents)
let endOfWeek = calendar.date(byAdding: .day, value: 6, to: startOfWeek!)
let formatter = DateFormatter()
formatter.dateStyle = .short
print(formatter.string(from: startOfWeek!))
print(formatter.string(from: endOfWeek!))
It prints this:
1/8/17
1/14/17
When I change the code to this:
dateComponents.weekOfYear = 1
dateComponents.year = 2017
It prints this:
12/31/17
1/6/18
Why is it 12/31/17?
When I use .full style to print the dates, I get Sunday, December 31, 2017 for the first date, but it's obviously wrong because December 31 is a Thursday.
If you want to get the correct date, use yearForWeekOfYear instead of year. Docs:
You can use the yearForWeekOfYear property with the weekOfYear and weekday properties to get the date corresponding to a particular weekday of a given week of a year. For example, the 6th day of the 53rd week of the year 2005 (ISO 2005-W53-6) corresponds to Sat 1 January 2005 on the Gregorian calendar.
Alternative, you can be a little naughty and not listen to the docs and use weekOfYear = 54:
let calendar = Calendar.current
var dateComponents = DateComponents()
dateComponents.weekday = calendar.firstWeekday
dateComponents.weekOfYear = 54
dateComponents.year = 2017
let startOfWeek = calendar.date(from: dateComponents)
let endOfWeek = calendar.date(byAdding: .day, value: 6, to: startOfWeek!)
let formatter = DateFormatter()
formatter.dateStyle = .short
print(formatter.string(from: startOfWeek!))
print(formatter.string(from: endOfWeek!))
This prints:
1/1/17
1/7/17
which is coincidentally, the correct dates.

Swift DateIntervalFormatter: keep dates out of the string even if interval spans multiple days

I am trying to format a time interval. I want a result that looks like this:
10:45 - 12:00 AM
I can get very close to this using DateInvervalFormatter:
let cal = Calendar.current
let formatter = DateIntervalFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
let start = Date()
let end = cal.date(byAdding: .hour, value: 1, to: start)
formatter.string(from: DateInterval(start: start, end: end!))
The above (in the en_US locale) will produce an output such as:
5:27 – 6:27 PM
Looks good right? However, this does not work if the two dates in the interval are on different days. For example:
let formatter = DateIntervalFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .short
let startComponents = DateComponents(year: 2020, month: 1, day: 1, hour: 23, minute: 45)
let start = cal.date(from: startComponents)
let end = cal.date(byAdding: .hour, value: 1, to: start!)
formatter.string(from: DateInterval(start: start!, end: end!))
Despite setting dateStyle to .none, the string produced in the above example (in the en_US locale) is:
1/1/2020, 11:45 PM – 1/2/2020, 12:45 AM
What I want is:
11:45 PM – 12:45 AM
How can I get this? I know I could use a DateFormatter to format each date (start and end) into just a time, and then append the two strings together with a hyphen (-) in the middle, but this is not necessarily localization-friendly.
What I ended up with is:
extension Date {
func formatTimeInterval(
to: Date,
timeZone: TimeZone = .autoupdatingCurrent
) -> String {
let formatter = DateIntervalFormatter()
formatter.timeZone = timeZone
formatter.timeStyle = .short
formatter.dateStyle = .none
// we need to manually strip any "date" metadata, because for some locales,
// even if we set `formatter.dateStyle = .none`, if the dates are in two different days
// we will still get the date information in the end result (e.g. for the US locale)
let calendar = Calendar.current
let fromComponents = calendar.dateComponents([.hour, .minute, .second], from: self)
let toComponents = calendar.dateComponents([.hour, .minute, .second], from: to)
let fromDate = calendar.date(from: fromComponents)!
let toDate = calendar.date(from: toComponents)!
return formatter.string(from: fromDate, to: toDate)
}
}
However, if the interval dates are in different months (say 31st August - 1 September), it adds a date starting at year 1, something like
1/1/1, 11:45 PM – 1/2/1, 12:45 AM
No clean solution to this still...

How to check how many times the clock has shown a full hour between two dates?

I want to check how many full calendar hours have passed between two dates.
15:00 -> 15:00 = 0 Hours
15:00 -> 16:00 = 1 Hour
15:30 -> 16:15 = 1 Hour
16:45 -> 18:10 = 2 Hours
I don`t want to know the total hours that you can get with:
Calendar.current.dateComponents([.hour], from: date, to: date2).hour
Any ideas how to do this?
First compute the start of the hour for both dates:
let start1 = cal.dateInterval(of: .hour, for: date1)?.start
let start2 = cal.dateInterval(of: .hour, for: date2)?.start
Use these to compute the difference in hours, as before.
Full self-contained example:
let cal = Calendar.current
let date1 = DateComponents(calendar: cal, year: 2018, month: 6, day: 15, hour: 16, minute: 45).date!
let date2 = DateComponents(calendar: cal, year: 2018, month: 6, day: 15, hour: 18, minute: 10).date!
let start1 = cal.dateInterval(of: .hour, for: date1)!.start
let start2 = cal.dateInterval(of: .hour, for: date2)!.start
let hours = cal.dateComponents([.hour], from: start1, to: start2).hour!
print(hours) // 2
Take a look at the DateComponentsFormatter. More specifically, you might want to do something like this:
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.includesApproximationPhrase = false
formatter.includesTimeRemainingPhrase = false
formatter.allowedUnits = [.hour]
let string = formatter.string(from: date, to: date2) //prints out "2 hours"
Source

NSDate startOfDay is 4AM?

I'm trying to write a loop for every 10 minutes of a given 24 hour day, starting at midnight and ending at ten minutes before midnight. So I tried this...
let x = Calendar.current.component(.year, from: Date())
let dateFormatter = DateFormatter()
dateFormatter.dateFormat="dd-MM-yyyy"
let june = dateFormatter.date(from: "21-06-" + String(x))
The result for june is "2017-06-21 04:00:00 UTC". Now technically this is correct, my local day will be 4 AM UTZ, but the code I'm passing this into, from the Astronomical Almanac, already handles local/global conversion.
So then I tried this:
var UTZCal = Calendar.current
UTZCal.timeZone = TimeZone(abbreviation: "GMT")!
let x = UTZCal.component(.year, from: Date())
let dateFormatter = DateFormatter()
dateFormatter.dateFormat="dd-MM-yyyy"
dateFormatter.calendar = UTZCal
let june = dateFormatter.date(from: "21-06-" + String(x))
This produced the exact same result. What am I missing?
It seems that the date formatter does not use the timezone of the
assigned calendar, and adding
dateFormatter.timeZone = UTZCal.timeZone
to your code makes it produce the expected result. But note that you
can simplify the calculation to
var utzCal = Calendar(identifier: .gregorian)
utzCal.timeZone = TimeZone(secondsFromGMT: 0)!
let year = utzCal.component(.year, from: Date())
let june = DateComponents(calendar: utzCal, year: year, month: 6, day: 21).date!
print(june) // 2017-06-21 00:00:00 +0000

Swift get local date instance

please help me to get local date and the start of the day, I mean the midnight. For getting local date I'm using code below, but I dont think it is right
var calendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)
calendar!.timeZone = NSTimeZone.localTimeZone()
let components = NSDateComponents()
components.second = NSTimeZone.localTimeZone().secondsFromGMT
let today = calendar!.dateByAddingComponents(components, toDate: NSDate(), options: nil)
But I can't get the midnight of the day, it keeps returning time 21.00
var comps = calendar!.components(NSCalendarUnit.YearCalendarUnit | .MonthCalendarUnit | .DayCalendarUnit | .HourCalendarUnit | .MinuteCalendarUnit | .SecondCalendarUnit, fromDate: today!)
comps.hour = 0
comps.minute = 0
comps.second = 0
let startToday = calendar!.dateFromComponents(comps)!
even this return 21.00
calendar!.startOfDayForDate(today)
NSDate does not have an attached timezone. For some head-scratching reason, Apple decided to always display the date in GMT when you print it. After enough hair loss on this, I decided to write my own description method see the date in my local time zone:
extension NSDate {
public var localTimeString : String {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
formatter.timeZone = NSTimeZone.localTimeZone()
return formatter.stringFromDate(self)
}
}
let now = NSDate()
let flags : NSCalendarUnit = [.Day, .Month, .Year]
let gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let components = gregorian.components(flags, fromDate: now)
let today12AM = gregorian.dateFromComponents(components)!
print(today12AM)
print(today12AM.localTimeString)
Edit:
You can construct a point in time manually:
let x = gregorian.dateWithEra(1, year: 2015, month: 8, day: 15, hour: 0, minute: 0, second: 0, nanosecond: 0)!
print(x)
print(x.localTimeString)
print(x.timeIntervalSince1970) // seconds since midnight Jan 1, 1970
print(today12AM.timeIntervalSince1970)
x is no different than today12AM. As I said, NSDate has no built-in timezone. When you use print, it converts your date into GMT. That's why it appears superficially different from the same point in time when expressed in your own timezone.