Retrieve date from double value in NSUserDefaults (Swift) - swift

Hi I'm creating an that has two controls: A NSSlider and a NSTextField that update continuously. I added a NSDateFormatter to the NSTextField so that it shows the current value of the slider formatted as a time. The slider has a range of 0 to 86,400 units (so that the user slides the seconds in a range of 24 hours).
I connected my app to the shared user defaults controller so that the value is stored for the user.
The app shows the correct time in the NSTextField, but it always stores the value in seconds. for example, if the user selected 06:32:14 when moving the slider, the system will actually store 23534.2261709793 in the key startTime
When the user opens the app again, they will see 06:32:14 as expected.
My problem is when I want to read the variable in my code:
I can read the value as Double:
let value = NSUserDefaults.standardUserDefaults().objectForKey("startTimeValue") as? Double
This works fine, if I try to cast it as NSDate it won't work (the value will be set to nil). So I thought that using a NSDateFormatter in code could work (as this is the way interface builder does the work for the user to see the date):
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = NSDateFormatterStyle.NoStyle
dateFormatter.timeStyle = NSDateFormatterStyle.ShortStyle
let date = dateFormatter.dateFromString((value?.description)!)
In the previous code I created the date formatter, then set the date style to nothing (I only want to do the time), and the time style to short. But giving the value.description ("23534.2261709793") still won't work. date will still be nil.
Is there something I'm doing wrong?

The double that you are storing / getting from NSUserDefaults is the time interval from Thursday 1st January 1970. So you need to feed that time interval into the NSDate constructor so that it can construct the date for you.
let value = NSUserDefaults.standardUserDefaults().objectForKey("startTimeValue") as? Double
let date = NSDate(timeIntervalSince1970: value)

Related

DateFormatter returns previous day

I've looked around and people have had problems with different years, random and changing results, and nil dates, but nothing like what I have, so I am asking here. Note I am in Playground right now.
I am taking strings in the format of "yyyy-mm-dd" and converting them to a different date format. Here is the code:
let example = "2001-11-03"
let dateFormatterInput = ISO8601DateFormatter()
dateFormatterInput.formatOptions = [.withFullDate, .withDashSeparatorInDate]
let date = dateFormatterInput.date(from: example)
let dateFormatterOutput = DateFormatter()
dateFormatterOutput.dateFormat = "MMMM dd, yyyy"
let output = dateFormatterOutput.string(from: date!)
The sidebar in Playground shows that the first reference to the previous day's date happens on the let date line. Also, this behavior happens on every date I've tried. In this example, it returns "November 2, 2001." I've tried different months, days, and years (1900s and 2000s) and it gives me the same result every time.
What am I doing wrong?
The key thing here is that ISO8601DateFormatter by default thinks that the time zone of your date string is GMT:
ISO8601DateFormatter.timeZone:
The time zone used to create and parse date representations. When unspecified, GMT is used.
However, the timeZone of DateFormatter by default (and also the side bar of the playground) assumes your device's local time zone:
DateFormatter.timeZone
The time zone for the receiver. If unspecified, the system time zone is used.
If your system time zone has a negative UTC offset on the start of the day 2001-11-03 UTC, then when seen from your time zone, that moment is actually in the day 2001-11-02. Hence the output you see.
Assuming you don't care about the actual value of date, and just care about the final string output, you can just set the timeZone of DateFormatter to GMT:
dateFormatterOutput.timeZone = TimeZone(identifier: "GMT")
Side note: You should also set locale when using a fixed format to avoid localisation issues:
dateFormatterOutput.locale = Locale(identifier: "en_US_POSIX")
Or better, just use one of the built-in, locale sensitive formats instead:
dateFormatterOutput.dateStyle = .medium
dateFormatterOutput.timeStyle = .none

Why my Cloud records Time isn't the same when I load it into my app

In a training project to learn the cloudKit capacity I record a date via a Datepicker and I save it into a cloudKit data base. I use the French format "DD/MM/YYYY HH:MM". Everything work fine and the date which is entered by the user is the same format in the data base.
Now I would like to work with this date in an other view in my app. So I load it into an array from cloudKit and I would like to compare it with the current time.
let recordTime = LastMealRecords[LastMealRecords.count - 1]
let currentSavedTime = (recordTime.object(forKey: "Timming"))
let diffComponents = Calendar.current.dateComponents([.hour], from: currentSavedTime as! Date, to: Now)
let intervals = diffComponents.hour
print(Now)
print(currentSavedTime)
So here the print show :
Now : DD/MM/YYYY HH:MM (which is the correct local time !)
currentSavedTime : DD/MM/YYY HH:MM (but HH:MM is not the same value as the one which is save on the Cloud data base. In fact it seem to be the UTC time cause there is 2h difference in less like the Local and UTC time in France... )
Question : How could I fixe this matter ? I'm trying to find the same value which is saved on the cloud..
Thanks for your help !
You can't make changes to the time saved at server end, all you can do is manipulate your current time accordingly
// Return time zone used by the system right away
let timeZone = NSTimeZone.system
// Returns the difference in seconds between the server and GMT at a given date.
let timeZoneOffset = timeZone.secondsFromGMT(for: currentDate) / 3600
print(timeZoneOffset, "hours offset for timezone", timeZone)

Swift - How to create a date object containing just the time

I trying to create a date object just containing the time of 1 second past midnight.
I believe the following should work but it just keeps returning nil.
let dateTime = Date()
let timeFormatter = DateFormatter()
timeFormatter.dateFormat = "HH:mm:ss"
let time = timeFormatter.date(from: "00:00:01")
print("Time: \(time!)")
Can someone tell me what i'm doing wrong!
Thanks
Let Calendar do the math, this is more reliable, you aren't using the current date (dateTime) anyway.
let midnight = Calendar.current.startOfDay(for: Date())
let oneSecondAfterMidnight = Calendar.current.date(byAdding: .second, value: 1, to: midnight)
This works even if midnight doesn't exist due to daylight saving change.
Date is not a "date" in any meaningful way. It's a specific point in time, independent of any calendar or location. What you want to express is a point on a calendar: "one second" past an arbitrary calendar point we call "midnight." That's done with DateComponents.
var dc = DateComponents()
dc.hour = 0
dc.minute = 0
dc.second = 1
This is the second second of the first minute of the first hour (00:00:01) of an arbitrary day on an arbitrary calendar, which is what you've described.
More precisely, it's "zero hours, zero minutes, and one second," which is only "one second after midnight" if you add it to some "midnight." But beyond that, there is no independent "time" type. Those things only have meaning when applied to a Calendar.
(Keep in mind that due to DST change in some parts of world, such as Iran, there are sometimes two midnights in the same day. So when you ask for this kind of thing, you need to be very clear what you mean. Do you want every second after midnight or just the first one on a given day?)

Picking a date with swift UI automation

In the app I'm testing there is a date picker I'm trying to automate. The wheel defaults to tomorrow and I'm attempting to change it to today's date but 2 minutes from now. Below is the code I'm using to attempt this.
app.pickerWheels.element(boundBy: 0).adjust(toPickerWheelValue: "Today")
app.pickerWheels.element(boundBy: 1).adjust(toPickerWheelValue: "1")
app.pickerWheels.element(boundBy: 2).adjust(toPickerWheelValue: "00")
(In the actual code I'm using variables and not hard coding these string)
This code works for the second and third wheel (hours and minutes) but for the first wheel it won't set the value. The test will fail and not continue past that point.
I have also tried passing today's date instead of just "Today" with the same results.
You can use the DateFormatter class with Date to accomplish this.
// Initialize the date formatter. Set the timeZone and format. I chose hours and minutes.
let dateFormatter: DateFormatter = DateFormatter()
dateFormatter.timeZone = NSTimeZone.local
dateFormatter.dateFormat = "HH:mm"
// Initialize the Date instance using a time interval since now.
let d: Date = Date(timeIntervalSinceNow: 2 * 60)
print("Current Time = \(dateFormatter.string(from: Date())), Two-Minutes-From-Now = \(dateFormatter.string(from: d))")
Output: Current Time = 23:57, Two-Minutes-From-Now = 23:59
A Date is stored as a time interval since January 1st, 1970. You can manipulate the date by adding or subtracting seconds from it. Here, I added 2 * 60 or two 60-second minutes to the current time interval (a large value represented in a double). This points to two minutes in the future.
Now, if you print the date without the formatter, it will just display the current time with no regard to your time zone. So if you want it to be accurate to your time zone, you need to set that in the formatter first. Note that it doesn't change the time, just its representation to you.

Set maximum time with UIDatePicker - time mode - swift

Is it possible to customise the UIDatePicker in CountDownTimer mode to not show the picker values past a certain point? For example if I set it to 2 hours it would only show 2 hours downwards.
Currently I have tried a few techniques but all I have succeeded in is setting the countdown point to a certain value using maximumDate.
self.datePickerView.datePickerMode = UIDatePickerMode.CountDownTimer
//For calculating a date with +30 mins
let calendar = NSCalendar.currentCalendar()
let date = calendar.dateByAddingUnit(.CalendarUnitMinute, value: 30, toDate: NSDate(), options: nil)
println("30+Mins \(date)")
//Interval for countdownDuration
let myTimeInterval = NSTimeInterval(timeOffset * 60)
println("myTimeInterval \(myTimeInterval)")
//Tried Methods - countDownDuration sets it to a certain points(in seconds)
self.datePickerView.countDownDuration = myTimeInterval
self.datePickerView.maximumDate = date // This doesn't seem to do anything when in using UIDatePickerMode.CountDownTimer
I presume a possible way is to use just a UIPicker.
Exactly as you say - it's possible to do it yourself with UIPickerView. And that's actually exactly what I did: I built a custom UIPickerView subclass for that purpose. It replicates the functionality of UIDatePicker in countDownTimer mode, while adding support to set maxTimeInterval.
You use it like this:
GSTimeIntervalPicker *picker = [[GSTimeIntervalPicker alloc] init];
picker.maxTimeInterval = (3600 * 3); // set the limit
picker.minuteInterval = 5; // the step. Default is 1 min.
picker.timeInterval = (3600 * 1.5); // 1 h 30 minutes
picker.onTimeIntervalChanged = ^(NSTimeInterval newTimeInterval) {
// Use the value
};
Available on GitHub under MIT license. Blog post here. I'm sorry it's not in Swift, you will have to convert it yourself.
First Thing: you don't need a calendar to add 30 minutes to a given date.
There is a constructor that does what you want in one step:
NSDate(timeInterval: NSTimeInterval, sinceDate: NSDate)
NSDate(timeIntervalSinceNow: NSTimeInterval)
for me,
self.datePickerView.maximumDate = date
shows future dates / time greyed out, so the user has some visual hint.
This doesn't mean, a future date / time is not selectable without additional code.
The user can select a greyed out time. You can check in your handler and when some future date is selected, you just set
self.datePickerView.date = date
as long as you don't call resignFirstResponder(), your date picker scrolls back to now (or any other chosen time) nicely. So the user is not able to select a time in the future with this additional code.
I hope this is near enough to what you need. Future is greyed out instead of invisible and it is not selectable with a little additional code in your handler.