Swift get local date instance - swift

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.

Related

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...

Creating a date with DateComponents

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))

How to convert 2 Strings (date and time) into one NSDate object in Eastern Time Zone in Swift

I have the following code to convert a String with a date like April 22, 2017 and a second String with the time 4:30 PM and I need to convert these 2 Strings into one complete NSDate object in the Eastern Time Zone.
//convert scheduled date and time from String to NSDate
let scheduledServiceDateStr = orderReview.serviceDate //current format example: April 21, 2017
let scheduledServiceTimeStr = orderReview.serviceTime //current format example: 04:30 PM
print("scheduledServiceDateStr: \(scheduledServiceDateStr)")
print("scheduledServiceTimeStr: \(scheduledServiceTimeStr)")
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MMMM d, yyyy"
dateFormatter.timeZone = NSTimeZone(name: "EST")
let scheduledServiceDate = dateFormatter.dateFromString(scheduledServiceDateStr)
print("scheduledServiceDate: \(scheduledServiceDate)")
let timeFormatter = NSDateFormatter()
timeFormatter.dateFormat = "hh:mm a"
timeFormatter.timeZone = NSTimeZone(name: "EST")
let scheduledServiceTime = timeFormatter.dateFromString(scheduledServiceTimeStr)
print("scheduledServiceTime: \(scheduledServiceTime)")
let timeComponents = NSCalendar.currentCalendar().components([.Hour, .Minute, .Second], fromDate: scheduledServiceTime!)
let dateComponents = NSCalendar.currentCalendar().components([.Year, .Month, .Day, .Hour, .Minute, .Second], fromDate: scheduledServiceDate!)
// append the time to the date
dateComponents.hour = timeComponents.hour
dateComponents.minute = timeComponents.minute
dateComponents.second = timeComponents.second
let dateAndTime = NSCalendar.currentCalendar().dateFromComponents(dateComponents)
print("dateAndTime: \(dateAndTime)")
This is what is printed in the Console:
scheduledServiceDateStr: April 22, 2017
scheduledServiceTimeStr: 04:30 PM
scheduledServiceDate: Optional(2017-04-22 05:00:00 +0000)
scheduledServiceTime: Optional(2000-01-01 21:30:00 +0000)
dateAndTime: Optional(2017-04-21 20:30:00 +0000)
The issue I am having is that the ending result, the dateAndTime NSDate value displayed in the console at the end is 2017-04-21 20:30:00 +0000 but, the time 20:30:00 I believe is equivalent to 8:30 PM Eastern Time when the time originally selected was 4:30 PM. I understand that NSDate does not take Time Zones into account and that it is just a representation of of seconds since January 1, 2001. As you can see I am setting the Time Zone to EST before converting each String to an NSDate object. What am I doing wrong?
I don't think there's anything wrong with the time. EST is -04:00 so 16:30 EST = 20:30 GMT. And you wrote way more code that needed to be:
let scheduledServiceDateStr = "April 21, 2017"
let scheduledServiceTimeStr = "04:30 PM"
let formatter = DateFormatter()
formatter.dateFormat = "MMM dd, yyyy hh:mm a"
formatter.timeZone = TimeZone(identifier: "EST")
if let date = formatter.date(from: scheduledServiceDateStr + " " + scheduledServiceTimeStr) {
print(date)
}

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

Getting first week of the Year NSCalendar

I am trying to put the dates of the beginning of the week in a app.
However in 2017, this results in un-expected behavior, as this year has the week '1' 2 times in it,
starting Sunday Jan 1st, and Sun Dec 31.
My program finds the latter. Giving me the result of
'01-01-2018' where is should be, '02-01-2017'
How can i make sure my program finds the first date available?
this is my code:
getFirstDay(Int(weekNummer)!, year: yearWrap)
func getFirstDay(weekNumber:Int, year:Int)->String?{
let Calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let dayComponent = NSDateComponents()
dayComponent.weekOfYear = weekNumber
dayComponent.weekday = 2
dayComponent.year = year
var date = Calendar.dateFromComponents(dayComponent)
if ((weekNumber == 1 || weekNumber == 52 || weekNumber == 53) && Calendar.components(.Month, fromDate: date!).month != 1 ){
dayComponent.year = year - 1
date = Calendar.dateFromComponents(dayComponent)
}
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
return String(dateFormatter.stringFromDate(date!))
}
Let do NSCalendar the complete date math
func firstDayOfWeek(week: Int, inYear year : Int) -> NSString {
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let components = NSDateComponents()
components.weekOfYear = week
components.yearForWeekOfYear = year
components.weekday = 2 // First weekday of the week (Sunday = 1)
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
let date = calendar.dateFromComponents(components)!
return dateFormatter.stringFromDate(date)
}
firstDayOfWeek(1, inYear : 2017) // 02-01-2017
firstDayOfWeek(1, inYear : 2018) // 01-01.2018