I'll keep this as short and simple as I can.
I have an NSDate extension that I use for a number of things throughout my app, but I have run into a problem with (strangely) a very simple function.
The relevant part of the extension is:
import Foundation
extension NSDate {
func nineAMMonday() -> NSDate {
let greg = NSCalendar.currentCalendar()
let componenets = NSDateComponents()
componenets.weekday = 2
componenets.hour = 9
componenets.minute = 0
componenets.second = 0
return greg.dateFromComponents(componenets)!
}
}
Its purpose is simply to return an NSDate which is referring to a Monday morning.
So far so good...
The issue is when I try to call this function, I get an error:
Missing Argument For Parameter #1 In Call
I read here https://stackoverflow.com/a/26156765/3100991 about someone who had a similar problem, however their code was long and confusing and I didn't understand the answer. I think it would be useful for the general community if a simple explanation and fix was provided.
Thanks for your help in advance!
Loic
I just try that (in the playground) and it works.
I think your error is in the call of your method.
since your method is an instance method you have to call it on an existing date object.
So try to call it like that:
import Foundation
extension NSDate {
func nineAMMonday() -> NSDate {
let greg = NSCalendar.currentCalendar()
let componenets = NSDateComponents()
componenets.weekday = 2
componenets.hour = 9
componenets.minute = 0
componenets.second = 0
return greg.dateFromComponents(componenets)!
}
}
// Call
var date = NSDate();
date.nineAMMonday()
But in your case the best thing to do is a class func.
import Foundation
extension NSDate {
class func nineAMMonday() -> NSDate {
let greg = NSCalendar.currentCalendar()
let componenets = NSDateComponents()
componenets.weekday = 2
componenets.hour = 9
componenets.minute = 0
componenets.second = 0
return greg.dateFromComponents(componenets)!
}
}
// Call
NSDate.nineAMMonday()
Related
I am working on a project with NSManagedObjects where each object is a user entry that has a value and a date. The app will be running a 7 day average and a 14 day average of the values the user enters. I have already set it up to do the appropriate fetch requests, sum the values, and divide by 7 and 14, respectively. However, I am realizing that when the user first begins using the app these running average values will be very misleading, so I would like to set up a function that will evaluate the number of days out of the last 7 and 14 that do not have any entries so I can subtract that value from the denominator in these calculations. I am a relative beginner and am having a hard time getting my head around how to write this function though, so any help would be appreciated. Thanks in advance.
Edit in response to Drekka:
The code I'm working from is below. I'm sorry for the broad question but I can't quite figure out where to start with structuring a looping function for what I'm trying to do and I haven't been able to come up with any examples or analogues in the searching I've done. Basically I'm pulling all of the values entered in the last seven days but I'm trying to figure out a way to evaluate for days where no values were entered within the span of this fetch request.
func sevenDayFetch() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Entry")
var calendar = Calendar.current
calendar.timeZone = NSTimeZone.local
let sevenDaysAgo = calendar.date(byAdding: .day, value: -7, to: Date())
let dateFrom = calendar.startOfDay(for: sevenDaysAgo!)
let dateTo = Date()
let fromPredicate = NSPredicate(format: "entryDate > %#", dateFrom as NSDate)
let toPredicate = NSPredicate(format: "entryDate <= %#", dateTo as NSDate)
let sevenDayPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [fromPredicate, toPredicate])
fetchRequest.predicate = sevenDayPredicate
do {
entryArray = try managedContext.fetch(fetchRequest)
var sevenDayArray: [Int] = []
for i in entryArray as [NSManagedObject] {
sevenDayArray.append(i.value(forKey: "Value") as! Int)
}
let sevenDaySum = sevenDayArray.reduce(0, +)
let sevenDayAverage = sevenDaySum/7
sevenDayAverageLabel.text = String(sevenDayAverage)
I find it helpful to save the install date of the app in UserDefaults.
func installDate() -> NSDate {
var installDate: Date
if let date = UserDefaults.standard.object(forKey: UserDefaultsKeys.dateInstalled) as? Date {
installDate = date
} else {
installDate = Date()
UserDefaults.standard.set(installDate, forKey: UserDefaultsKeys.dateInstalled)
}
return installDate
}
Core data is not well suited for storing a single global value. And you cannot infer the install date from core-data; not having a value for a date does not mean that the was not install then.
Once you know the install date you can adjust calculations and your UI, if it is less than 14 or 7 days.
My idea is to make an ios-app that displays today's lunch at my work. I'm not sure how to approach this. My own thought was to have a UIDatePicker (default set to current day) and have functions that will respond to different dates. This is some code just to illustrate the ideas in my head.
var dateFromPicker = UIDatePicker.date
#IBOutlet weak var lunchLabel: UILabel!
func februaryFirst {
let dateFebruaryFirst = ...
if dateFromPicker = dateFebruaryFirst {
lunchLabel.text = ("Fish'n chips")
}
}
func februarySecond {
let dateFebruarySecond = ...
if dateFromPicker = dateFebruarySecond {
lunchLabel.text = ("Noodlesoup")
}
}
You probably want to use DateComponents to check which day/month a certain date is. For example:
func februarySecond {
// Get the day and month of the given date
let dateFromPickerComponents = Calendar.current.dateComponents([.day, .month], from: dateFromPicker)
// Check whether the day and month match Feb 2
if dateFromPickerComponents.day == 2 && dateFromPickerComponents.month == 2 {
lunchLabel.text = ("Noodlesoup")
}
}
You could use a switch statement which supports multiple values:
let dateFromPicker = UIDatePicker.date
let components = Calendar.current.dateComponents([.month, .day], from: dateFromPicker)
switch (components.month!, components.day!) { // first month then day
case (1,14): print("suprise")
case (2,1): print("Fish'n chips")
case (2,2): print("Noodlesoup")
default: print("fast day")
}
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
I am making one app that can run AppleScript via NSAppleScript.
Everything had been fine but I haven't been able to figure out how to pass date information from my app to AppleScript. (Since AppleScript has date type, I suppose this is possible)
The way I pass parameters to AppleScript is through NSAppleEventDescriptor. I learned from Google that I could pass it as typeLongDateTime type:
- (id)initWithDate:(NSDate *)date {
LongDateTime ldt;
UCConvertCFAbsoluteTimeToLongDateTime(CFDateGetAbsoluteTime((CFDateRef)date), &ldt);
return [self initWithDescriptorType:typeLongDateTime
bytes:&ldt
length:sizeof(ldt)];
}
Unfortunately, the type LongDateTime had long gone, because I am using Swift and under OS X 10.10. Even the Core Services function UCConvertCFAbsoluteTimeToLongDateTime has already been removed from 10.10.3.
Now I am stuck.
Do you have any ideas that inspire?
Is seems that LongDateTime is a signed 64-bit integer which represents
a date d as the number of seconds since January 1, 1904, GMT, adjusted by the time-zone offset
for d in the local time zone (including daylight-savings time
offset if DST is active at d).
The following code gives the same result as your Objective-C code for all dates that I tested (winter time and summer time).
class DateEventDescriptor : NSAppleEventDescriptor {
convenience init?(date : NSDate) {
let secondsSince2001 = Int64(date.timeIntervalSinceReferenceDate)
var secondsSince1904 = secondsSince2001 + 3061152000
secondsSince1904 += Int64(NSTimeZone.localTimeZone().secondsFromGMTForDate(date))
self.init(descriptorType: DescType(typeLongDateTime),
bytes: &secondsSince1904, length: sizeofValue(secondsSince1904))
}
}
Update for Swift 3:
class DateEventDescriptor : NSAppleEventDescriptor {
convenience init?(date: Date) {
let secondsSince2001 = Int64(date.timeIntervalSinceReferenceDate)
var secondsSince1904 = secondsSince2001 + 3061152000
secondsSince1904 += Int64(NSTimeZone.local.secondsFromGMT(for: date))
self.init(descriptorType: DescType(typeLongDateTime),
bytes: &secondsSince1904, length: MemoryLayout.size(ofValue: secondsSince1904))
}
}
Update for macOS 10.11:
As of macOS 10.11 there is a
NSAppleEventDescriptor(date: Date)
initializer, so that the above workaround is no longer necessary.
(Thanks to #Wevah for this information.)
Inspired by Martin, I got to know that the LongDateTime type is just something that records time interval since the date 1904-01-01 midnight. And AppleScript utilizes it to represent dates. However, one weird thing in AppleScript is that there is no time zone concept for date type. So, simply passing the time interval since 1904-01-01 00:00:00 +0000, would only make the resulted date in AppleScript show the time in GMT. That was why I tried Martin's suggestion but got wrong time shown from the AppleScript. Since it is a data involving time difference, I got the following way works for me:
convenience init?(date: NSDate) {
struct StaticWrapper {
static var longDateTimeReferenceDate: NSDate!
}
if StaticWrapper.longDateTimeReferenceDate == nil {
let formatter = NSDateFormatter()
let c = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
formatter.calendar = c
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
StaticWrapper.longDateTimeReferenceDate = formatter.dateFromString("1904-01-01 00:00:00")
}
var secondsSince1904 = Int64(date.timeIntervalSinceDate(StaticWrapper.longDateTimeReferenceDate))
self.init(descriptorType: DescType(typeLongDateTime), bytes: &secondsSince1904, length: sizeofValue(secondsSince1904))
}
The time zone information is not given in the date formatter, which implicitly includes the current time zone. Therefore, the resulted time interval will make the AppleScript to show the time in local time zone. Which behaves like the AppleScript command "current date".
There is a little-known CoreFoundation constant kCFAbsoluteTimeIntervalSince1904 representing the difference between 1904 and 2001. This NSDate extension converts NSDate to NSAppleEventDescriptor and vice versa
extension NSDate {
func appleScriptDate() -> NSAppleEventDescriptor
{
var secondsSince1904 = Int64(self.timeIntervalSinceReferenceDate + kCFAbsoluteTimeIntervalSince1904)
return NSAppleEventDescriptor(descriptorType: DescType(typeLongDateTime), bytes: &secondsSince1904, length: sizeofValue(secondsSince1904))!
}
convenience init(appleScriptDate : NSAppleEventDescriptor)
{
var secondsSince1904 : Int64 = 0
let data = appleScriptDate.data
data.getBytes(&secondsSince1904, length: data.length)
self.init(timeIntervalSinceReferenceDate:NSTimeInterval(secondsSince1904) - kCFAbsoluteTimeIntervalSince1904)
}
}
If you need to adjust the time zone information (converting to AppleScript date does not preserve the time zone) add NSTimeZone.systemTimeZone().secondsFromGMT in Swift or time to GMT in AppleScript
I updated vadian's extension for Swift 3:
extension NSDate {
func appleScriptDate() -> NSAppleEventDescriptor
{
var secondsSince1904 = Int64(self.timeIntervalSinceReferenceDate + kCFAbsoluteTimeIntervalSince1904)
return NSAppleEventDescriptor(descriptorType: DescType(typeLongDateTime), bytes: &secondsSince1904, length: MemoryLayout.size(ofValue: secondsSince1904))!
}
convenience init(appleScriptDate : NSAppleEventDescriptor)
{
var secondsSince1904 : Int64 = 0
withUnsafeMutablePointer(to: &secondsSince1904) {
_ = appleScriptDate.data.copyBytes(
to: UnsafeMutableBufferPointer(start: $0, count: 4),
from: 0..<4)
}
self.init(timeIntervalSinceReferenceDate:TimeInterval(secondsSince1904) - kCFAbsoluteTimeIntervalSince1904)
}
}
is there any possibility to create a custom log function?
This is how the default println("hello world") looks like:
2015-03-04 18:33:55.788 MyApp[12345:671253923] Hello World
I would like to output something like:
18:33 MyClass > myFunc [line 1] Hello World
First, for the time, you can get the current hour and minute as String:
func printTime()->String{
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour | .CalendarUnitMinute, fromDate: date)
let hour = components.hour
let minutes = components.minute
return "\(hour):\(minutes)"
}
And for the function etc. you can use the Swift Literal Expressions __FILE__, __FUNCTION__ and __LINE__.
But you don't want to set it each time you want to log. So you could do something like that:
func prettyPrint(print: String, file:String = __FILE__, functionName: String = __FUNCTION__, line:Int = __LINE__) {
println("\(printTime()) \(file) > \(functionName) [line \(line)] \(print)")
}
You call prettyPrint like that:
prettyPrint("hey")
And you will get the following output:
/Path/To/Your/File/MyClass.swift > hello [line 81] hey
But as you only want the name of your class, you can remove the path with the following function:
func getFile(path:String = __FILE__)->String{
var parts = path.componentsSeparatedByString("/")
return parts[parts.count-1]
}
Or, as ChikabuZ mentioned in his answer you can directly check the class:
let className = NSStringFromClass(self.classForCoder).pathExtension
Final Function
And here the final function(s):
func getFile(path:String = __FILE__)->String{
var parts = path.componentsSeparatedByString("/")
return parts[parts.count-1]
}
func prettyPrint(print: String, functionName: String = __FUNCTION__, line:Int = __LINE__) {
println("\(printTime()) \(getFile()) > \(functionName) [line \(line)] \(print)")
}
func printTime()->String{
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour | .CalendarUnitMinute, fromDate: date)
let hour = components.hour
let minutes = components.minute
return "\(hour):\(minutes)"
}
And the result will be:
MyClass.swift > hello [line 81] hey
You should also note #emaloney's answer to this question. Specifically that
println()-based solutions result in output being captured by the Apple System Log (ASL).
Ideally switch to NSLog or a full blown logging system
You should make an extension to NSObject, something like this:
class MyClass: NSObject
{
func myFunc()
{
myPrint("Hello World")
}
}
extension NSObject
{
func myPrint(text: String)
{
let timeFormatter = NSDateFormatter()
timeFormatter.dateStyle = NSDateFormatterStyle.NoStyle
timeFormatter.timeStyle = NSDateFormatterStyle.ShortStyle
let time = timeFormatter.stringFromDate(NSDate())
let className = NSStringFromClass(self.classForCoder).pathExtension
let function = __FUNCTION__
let line = "\(__LINE__)"
let result = time + " " + className + " > " + function + " " + line + " " + text
println(result)
}
}
let myClass = MyClass()
myClass.myFunc()
Unfortunately, none of the println()-based solutions result in output being captured by the Apple System Log (ASL).
The ASL is the logging facility provided by the Mac OS and iOS that is used by NSLog() (and on a Mac is visible through the Console application). Because NSLog() uses ASL, log entries recorded by NSLog() will be visible through the device console. Messages logged through println() will not be captured in ASL, and as a result, provide no opportunity to go back to the console for diagnostic purposes after something has happened.
There are two big downsides to NSLog(), however: (1) you can't customize the output format, and (2) it does a lot of work on the calling thread and can therefore negatively impact performance.
CleanroomLogger is a new Swift-based open-source API for logging. If you're familiar with log4j or CocoaLumberjack, then you'll understand CleanroomLogger.
You can customize your own output format by providing your own LogFormatter implementation to ensure that your log messages are formatted exactly as you want.
For more information on CleanroomLogger, visit the GitHub project page.