How to convert decimal-based time to NSDate? - objective-c

I have times that are represented as doubles. For example:
8:00am is 8.00
1:30pm is 13.50
6:15pm is 18.25
11:20pm is 23.333
How can I convert the doubles into an NSDate of today?

let input = 13.50
let hour = Int(input)
let minute = Int((input - Double(Int(input))) * 60)
let resultDate = NSCalendar.currentCalendar().dateBySettingHour(hour, minute: minute, second: 0, ofDate: NSDate(), options: nil)!

Get midnight today using this answer : How can I get an NSDate object for today at midnight?
NSDate *date = [NSDate date];
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
NSUInteger preservedComponents = (NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit);
date = [calendar dateFromComponents:[calendar components:preservedComponents fromDate:date]];
Then once you have the time at midnight, we will use addTimeInterval to add on the seconds from your decimal value.
int hoursToAdd = (int)timeDecimal;
int minutesToAdd = timeDecimal - hoursToAdd;
int secondsFromHours = hoursToAdd*60*60;
int secondsFromMinutes = minutesToAdd*60;
int totalSeconds = secondsFromHours + secondsFromMinutes;
NSDate *newDate = [date addTimeInterval:totalSeconds];
Should do the trick!

Start with midnight of today and add your amount of hours:
func dateFromDecimalHours(hours: Double) -> NSDate {
let midnight = NSCalendar.currentCalendar().startOfDayForDate(NSDate())
let date = midnight.dateByAddingTimeInterval(round(hours * 3600.0))
return date
}
(Most decimal fractions are not represented exactly by a Double,
that's why the time interval is rounded to seconds.)
Examples/test cases:
let fmt = NSDateFormatter()
fmt.dateFormat = "dd/MM/yyyy hh:mm.ss a"
let d1 = dateFromDecimalHours(18.25)
println(fmt.stringFromDate(d1)) // 07/05/2015 06:15.00 pm
let d2 = dateFromDecimalHours(23.3333)
println(fmt.stringFromDate(d2)) // 07/05/2015 11:20.00 pm
let d3 = dateFromDecimalHours(1.0 + 23.0/60.0 + 45.0/3600.0)
println(fmt.stringFromDate(d3)) // 07/05/2015 01:23.45 am

Related

Why is Excel serial date-time to NSDate conversion two days, one hour and 40 minutes ahead?

I'm converting dates from an Excel spreadsheet to NSDate's, but for some reason they always come out two days ahead: Sundays come out as Tuesdays, etc.
My conversion method is based on the following info from cpearson.com:
Excel stores dates and times as a number representing the number of
days since 1900-Jan-0, plus a fractional portion of a 24 hour day:
ddddd.tttttt . This is called a serial date, or serial date-time.
(...) The integer portion of the number, ddddd, represents the number
of days since 1900-Jan-0. (...) The fractional portion of the number,
ttttt, represents the fractional portion of a 24 hour day. For
example, 6:00 AM is stored as 0.25, or 25% of a 24 hour day.
Similarly, 6PM is stored at 0.75, or 75% percent of a 24 hour day.
- (NSDate *)dateFromExcelSerialDate:(double)serialdate
{
if (serialdate == 0)
return nil;
NSTimeInterval theTimeInterval;
NSInteger numberOfSecondsInOneDay = 86400;
double integral;
double fractional = modf(serialdate, &integral);
NSLog(#"%# %# \r serialdate = %f, integral = %f, fractional = %f",
[self class], NSStringFromSelector(_cmd),
serialdate, integral, fractional);
theTimeInterval = integral * numberOfSecondsInOneDay; //number of days
if (fractional > 0) {
theTimeInterval += numberOfSecondsInOneDay / fractional; //portion of one day
}
NSCalendar *nl_gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSTimeZone *nl_timezone = [[NSTimeZone alloc] initWithName:#"Europe/Amsterdam"];
[nl_gregorianCalendar setTimeZone:nl_timezone];
NSDateComponents *excelBaseDateComps = [[NSDateComponents alloc] init];
[excelBaseDateComps setMonth:1];
[excelBaseDateComps setDay:1];
[excelBaseDateComps setHour:00];
[excelBaseDateComps setMinute:00];
[excelBaseDateComps setTimeZone:nl_timezone];
[excelBaseDateComps setYear:1900];
NSDate *excelBaseDate = [nl_gregorianCalendar dateFromComponents:excelBaseDateComps];
NSDate *inputDate = [NSDate dateWithTimeInterval:theTimeInterval sinceDate:excelBaseDate];
NSLog(#"%# %# \r serialdate %f, theTimeInterval = %f \r inputDate = %#",
[self class], NSStringFromSelector(_cmd),
serialdate, theTimeInterval,
[self.nl_dateFormatter stringFromDate:inputDate]);
return inputDate;
}
The spreadsheet was produced in the Netherlands, presumably on a Dutch version of Microsoft Excel.
Spreadsheet date Sunday July 6, 2014 00:00 yields the following results:
dateFromExcelSerialDate:
serialdate = 41826.000000, integral = 41826.000000, fractional =
0.000000 theTimeInterval = 3613766400.000000 inputDate = 08 jul. 2014 01:40
Similarly, Sunday July 13, 2014 00:00 yields:
serialdate = 41833.000000, integral = 41833.000000, fractional =
0.000000 theTimeInterval = 3614371200.000000 inputDate = 15 jul. 2014 01:40
I can correct the output by subtracting 2 days, one hour and 40 minutes:
theTimeInterval -= ((60 * 60 * 24 * 2) + (60*60) + (60*40));
but I have no idea how robust that is.
That difference of two days made me think it had something to do with leap year corrections, so I tried to let the calendar do the calculations by adding the NSTimeInterval seconds to the excelBaseDate, like so:
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setSecond:theInterval];
NSDate *inputDate = [nl_gregorianCalendar dateByAddingComponents:comps
toDate:excelBaseDate
options:0];
Strangely enough, that gave me dates somewhere in the 1870's. Who knows what is going on?
there are two things here:
your start date is 1900-Jan-1 but your referred description clearly says: the reference is 1900-Jan-0 – you may add an extra day here;
year 1900 was not a leap-year – you may add an extra day here;
I guess, this is pretty much the reason why you get two extra days every occasion.
Microsoft knows about that, see more about the topic here.

LDAP Date to NSDate

I am trying to parse the pwdLastSet value from NSTask response when I do an ldapsearch. I've successfully extracted the value (129875475241190194) and I am trying to convert it to an NSDate Object.
Reference: http://www.chrisnowell.com/information_security_tools/date_converter/Windows_active_directory_date_converter.asp
I tried to extract the Javascript code from the page above and convert it but I am getting a different date.
int iYearsFrom1601to1970 = 1970 - 1601;
int iDaysFrom1601to1970 = iYearsFrom1601to1970 * 365;
iDaysFrom1601to1970 += (int)(iYearsFrom1601to1970 / 4); // leap years
iDaysFrom1601to1970 -= 3; // non-leap centuries (1700,1800,1900). 2000 is a leap century
float iSecondsFrom1601to1970 = iDaysFrom1601to1970 * 24 * 60 * 60;
int iTotalSecondsSince1601 = (int)(129875475241190194 / 10000000);
float iTotalSecondsSince1970 = iTotalSecondsSince1601 - iSecondsFrom1601to1970;
NSDate *date = [NSDate dateWithTimeIntervalSince1970:iTotalSecondsSince1970];
Any help would be appreciated.
Thanks!
Here's how I would do it:
NSDateComponents *base = [[NSDateComponents alloc] init];
[base setDay:1];
[base setMonth:1];
[base setYear:1601];
[base setEra:1]; // AD
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *baseDate = [gregorian dateFromComponents:base];
[base release];
[gregorian release];
NSTimeInterval timestamp = 129875475241190194.0 / 10000000.0;
NSDate *finalDate = [baseDate dateByAddingTimeInterval:timestamp];
This gives me a finalDate of 2012-07-24 03:58:22 +0000.
Since the timestamp is a time interval since Jan 1, 1601 at 00:00 UTC, you can use the -dateByAddingTimeInterval: method on NSDate to add the timestamp to the base date to get the final NSDate.
Once you've done that, you can run it through an NSDateFormatter to format it for display.
Assuming the, well, daring conversion between the basetimes is correct: actually looking at the warnings, instead of casting them away, might actually help:
int main(void)
{
int iTotalSecondsSince1601 = (129875475241190194 / 10000000);
return 0;
}
stieber#gatekeeper:~$ clang++ Test.cpp
Test.cpp:4:8: warning: implicit conversion from 'long' to 'int' changes value from 12987547524 to 102645636
....
That should account for a good deal of the difference...
Try this
NSTimeInterval value = 129875475241190194;
// instead of trying to compute seconds between 1601 and 1970
const NSTimeInterval EPOCH = 11644473600;
const NSTimeInterval NANO = 10000000;
NSTimeInterval seconds = value / NANO - EPOCH;
NSDate *answer = [NSDate dateWithTimeIntervalSince1970:seconds];
Also this is reason you don't want to calculate seconds since 1601: ...in the last millennium, 1600 and 2000 were leap years, but 1700, 1800 and 1900 were not. Excerpt from Wikipedia on Gregorian calendar.
The value for EPOCH is explained on Convert Active Directory "LastLogon:" time to (UNIX) readable time
.
Note: The information about accountExpires which starts from 12-31-1601 (11644473600). The values lastLogon and lastLogonTimeStamp however use 01-01-1601 as the date to calculate this value (11676009600).

Subtract 7 days from current date

It seems that I can't subtract 7 days from the current date. This is how i am doing it:
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setDay:-7];
NSDate *sevenDaysAgo = [gregorian dateByAddingComponents:offsetComponents toDate:[NSDate date] options:0];
SevenDaysAgo gets the same value as the current date.
Please help.
EDIT: In my code I forgot to replace the variable which gets the current date with the right one. So above code is functional.
code:
NSDate *currentDate = [NSDate date];
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
[dateComponents setDay:-7];
NSDate *sevenDaysAgo = [[NSCalendar currentCalendar] dateByAddingComponents:dateComponents toDate:currentDate options:0];
NSLog(#"\ncurrentDate: %#\nseven days ago: %#", currentDate, sevenDaysAgo);
[dateComponents release];
output:
currentDate: 2012-04-22 12:53:45 +0000
seven days ago: 2012-04-15 12:53:45 +0000
And I'm fully agree with JeremyP.
BR.
Eugene
If you're running at least iOS 8 or OS X 10.9, there's an even cleaner way:
NSDate *sevenDaysAgo = [[NSCalendar currentCalendar] dateByAddingUnit:NSCalendarUnitDay
value:-7
toDate:[NSDate date]
options:0];
Or, with Swift 2:
let sevenDaysAgo = NSCalendar.currentCalendar().dateByAddingUnit(.Day, value: -7,
toDate: NSDate(), options: NSCalendarOptions(rawValue: 0))
And with Swift 3 and up it gets even more compact:
let sevenDaysAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date())
use dateByAddingTimeInterval method:
NSDate *now = [NSDate date];
NSDate *sevenDaysAgo = [now dateByAddingTimeInterval:-7*24*60*60];
NSLog(#"7 days ago: %#", sevenDaysAgo);
output:
7 days ago: 2012-04-11 11:35:38 +0000
Swift 3
Calendar.current.date(byAdding: .day, value: -7, to: Date())
Swift operator extension:
extension Date {
static func -(lhs: Date, rhs: Int) -> Date {
return Calendar.current.date(byAdding: .day, value: -rhs, to: lhs)!
}
}
Usage
let today = Date()
let sevenDayAgo = today - 7
Swift 4.2 - Mutate (Update) Self
Here is another way the original poster can get one week ago if he already has a date variable (updates/mutates itself).
extension Date {
mutating func changeDays(by days: Int) {
self = Calendar.current.date(byAdding: .day, value: days, to: self)!
}
}
Usage
var myDate = Date() // Jan 08, 2019
myDate.changeDays(by: 7) // Jan 15, 2019
myDate.changeDays(by: 7) // Jan 22, 2019
myDate.changeDays(by: -1) // Jan 21, 2019
or
// Iterate through one week
for i in 1...7 {
myDate.changeDays(by: i)
// Do something
}
dymv's answer work great. This you can use in swift
extension NSDate {
static func changeDaysBy(days : Int) -> NSDate {
let currentDate = NSDate()
let dateComponents = NSDateComponents()
dateComponents.day = days
return NSCalendar.currentCalendar().dateByAddingComponents(dateComponents, toDate: currentDate, options: NSCalendarOptions(rawValue: 0))!
}
}
You can call this with
NSDate.changeDaysBy(-7) // Date week earlier
NSDate.changeDaysBy(14) // Date in next two weeks
Hope it helps and thx to dymv
Swift 4.2 iOS 11.x Babec's solution, pure Swift
extension Date {
static func changeDaysBy(days : Int) -> Date {
let currentDate = Date()
var dateComponents = DateComponents()
dateComponents.day = days
return Calendar.current.date(byAdding: dateComponents, to: currentDate)!
}
}
Swift 3.0+ version of the original accepted answer
Date().addingTimeInterval(-7 * 24 * 60 * 60)
However, this uses absolute values only. Use calendar answers is probably more suitable in most cases.
Swift 5
Function to add or subtract day from current date.
func addOrSubtructDay(day:Int)->Date{
return Calendar.current.date(byAdding: .day, value: day, to: Date())!
}
Now calling the function
var dayAddedDate = addOrSubtructDay(7)
var daySubtractedDate = addOrSubtructDay(-7)
To Add date pass prospective day value
To Subtract pass negative day
value
Swift 3:
A modification to Dov's answer.
extension Date {
func dateBeforeOrAfterFromToday(numberOfDays :Int?) -> Date {
let resultDate = Calendar.current.date(byAdding: .day, value: numberOfDays!, to: Date())!
return resultDate
}
}
Usage:
let dateBefore = Date().dateBeforeOrAfterFromToday(numberOfDays : -7)
let dateAfter = Date().dateBeforeOrAfterFromToday(numberOfDays : 7)
print ("dateBefore : \(dateBefore), dateAfter :\(dateAfter)")
FOR SWIFT 3.0
here is fucntion , you can reduce days , month ,day by any count
like for example here , i have reduced the current system date's year by 100 year , you can do it for day , month also
just set the counter
and store it in an array , you can this array anywhere then
func currentTime()
{
let date = Date()
let calendar = Calendar.current
var year = calendar.component(.year, from: date)
let month = calendar.component(.month, from: date)
let day = calendar.component(.day, from: date)
let pastyear = year - 100
var someInts = [Int]()
printLog(msg: "\(day):\(month):\(year)" )
for _ in pastyear...year {
year -= 1
print("\(year) ")
someInts.append(year)
}
print(someInts)
}

How to convert an NSTimeInterval (seconds) into minutes

I've got an amount of seconds that passed from a certain event. It's stored in a NSTimeInterval data type.
I want to convert it into minutes and seconds.
For example I have: "326.4" seconds and I want to convert it into the following string:
"5:26".
What is the best way to achieve this goal?
Thanks.
Brief Description
The answer from Brian Ramsay is more convenient if you only want to convert to minutes.
If you want Cocoa API do it for you and convert your NSTimeInterval not only to minutes but also to days, months, week, etc,... I think this is a more generic approach
Use NSCalendar method:
(NSDateComponents *)components:(NSUInteger)unitFlags fromDate:(NSDate *)startingDate toDate:(NSDate *)resultDate options:(NSUInteger)opts
"Returns, as an NSDateComponents object using specified components, the difference between two supplied dates". From the API documentation.
Create 2 NSDate whose difference is the NSTimeInterval you want to convert. (If your NSTimeInterval comes from comparing 2 NSDate you don't need to do this step, and you don't even need the NSTimeInterval).
Get your quotes from NSDateComponents
Sample Code
// The time interval
NSTimeInterval theTimeInterval = 326.4;
// Get the system calendar
NSCalendar *sysCalendar = [NSCalendar currentCalendar];
// Create the NSDates
NSDate *date1 = [[NSDate alloc] init];
NSDate *date2 = [[NSDate alloc] initWithTimeInterval:theTimeInterval sinceDate:date1];
// Get conversion to months, days, hours, minutes
unsigned int unitFlags = NSHourCalendarUnit | NSMinuteCalendarUnit | NSDayCalendarUnit | NSMonthCalendarUnit;
NSDateComponents *conversionInfo = [sysCalendar components:unitFlags fromDate:date1 toDate:date2 options:0];
NSLog(#"Conversion: %dmin %dhours %ddays %dmoths",[conversionInfo minute], [conversionInfo hour], [conversionInfo day], [conversionInfo month]);
[date1 release];
[date2 release];
Known issues
Too much for just a conversion, you are right, but that's how the API works.
My suggestion: if you get used to manage your time data using NSDate and NSCalendar, the API will do the hard work for you.
pseudo-code:
minutes = floor(326.4/60)
seconds = round(326.4 - minutes * 60)
All of these look more complicated than they need to be! Here is a short and sweet way to convert a time interval into hours, minutes and seconds:
NSTimeInterval timeInterval = 326.4;
long seconds = lroundf(timeInterval); // Since modulo operator (%) below needs int or long
int hour = seconds / 3600;
int mins = (seconds % 3600) / 60;
int secs = seconds % 60;
Note when you put a float into an int, you get floor() automatically, but you can add it to the first two if if makes you feel better :-)
Forgive me for being a Stack virgin... I'm not sure how to reply to Brian Ramsay's answer...
Using round will not work for second values between 59.5 and 59.99999. The second value will be 60 during this period. Use trunc instead...
double progress;
int minutes = floor(progress/60);
int seconds = trunc(progress - minutes * 60);
If you're targeting at or above iOS 8 or OS X 10.10, this just got a lot easier. The new NSDateComponentsFormatter class allows you to convert a given NSTimeInterval from its value in seconds to a localized string to show the user. For example:
Objective-C
NSTimeInterval interval = 326.4;
NSDateComponentsFormatter *componentFormatter = [[NSDateComponentsFormatter alloc] init];
componentFormatter.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
componentFormatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorDropAll;
NSString *formattedString = [componentFormatter stringFromTimeInterval:interval];
NSLog(#"%#",formattedString); // 5:26
Swift
let interval = 326.4
let componentFormatter = NSDateComponentsFormatter()
componentFormatter.unitsStyle = .Positional
componentFormatter.zeroFormattingBehavior = .DropAll
if let formattedString = componentFormatter.stringFromTimeInterval(interval) {
print(formattedString) // 5:26
}
NSDateCompnentsFormatter also allows for this output to be in longer forms. More info can be found in NSHipster's NSFormatter article. And depending on what classes you're already working with (if not NSTimeInterval), it may be more convenient to pass the formatter an instance of NSDateComponents, or two NSDate objects, which can be done as well via the following methods.
Objective-C
NSString *formattedString = [componentFormatter stringFromDate:<#(NSDate *)#> toDate:<#(NSDate *)#>];
NSString *formattedString = [componentFormatter stringFromDateComponents:<#(NSDateComponents *)#>];
Swift
if let formattedString = componentFormatter.stringFromDate(<#T##startDate: NSDate##NSDate#>, toDate: <#T##NSDate#>) {
// ...
}
if let formattedString = componentFormatter.stringFromDateComponents(<#T##components: NSDateComponents##NSDateComponents#>) {
// ...
}
Brian Ramsay’s code, de-pseudofied:
- (NSString*)formattedStringForDuration:(NSTimeInterval)duration
{
NSInteger minutes = floor(duration/60);
NSInteger seconds = round(duration - minutes * 60);
return [NSString stringWithFormat:#"%d:%02d", minutes, seconds];
}
Here's a Swift version:
func durationsBySecond(seconds s: Int) -> (days:Int,hours:Int,minutes:Int,seconds:Int) {
return (s / (24 * 3600),(s % (24 * 3600)) / 3600, s % 3600 / 60, s % 60)
}
Can be used like this:
let (d,h,m,s) = durationsBySecond(seconds: duration)
println("time left: \(d) days \(h) hours \(m) minutes \(s) seconds")
NSDate *timeLater = [NSDate dateWithTimeIntervalSinceNow:60*90];
NSTimeInterval duration = [timeLater timeIntervalSinceNow];
NSInteger hours = floor(duration/(60*60));
NSInteger minutes = floor((duration/60) - hours * 60);
NSInteger seconds = floor(duration - (minutes * 60) - (hours * 60 * 60));
NSLog(#"timeLater: %#", [dateFormatter stringFromDate:timeLater]);
NSLog(#"time left: %d hours %d minutes %d seconds", hours,minutes,seconds);
Outputs:
timeLater: 22:27
timeLeft: 1 hours 29 minutes 59 seconds
Since it's essentially a double...
Divide by 60.0 and extract the integral part and the fractional part.
The integral part will be the whole number of minutes.
Multiply the fractional part by 60.0 again.
The result will be the remaining seconds.
Remember that the original question is about a string output, not pseudo-code or individual string components.
I want to convert it into the following string: "5:26"
Many answers are missing the internationalization issues, and most doing the math computations by hand. All just so 20th century...
Do not do the Math yourself (Swift 4)
let timeInterval: TimeInterval = 326.4
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.unitsStyle = .positional
if let formatted = dateComponentsFormatter.string(from: timeInterval) {
print(formatted)
}
5:26
Leverage on libraries
If you really want individual components, and pleasantly readable code, check out SwiftDate:
import SwiftDate
...
if let minutes = Int(timeInterval).seconds.in(.minute) {
print("\(minutes)")
}
5
Credits to #mickmaccallum and #polarwar for adequate usage of DateComponentsFormatter
How I did this in Swift (including the string formatting to show it as "01:23"):
let totalSeconds: Double = someTimeInterval
let minutes = Int(floor(totalSeconds / 60))
let seconds = Int(round(totalSeconds % 60))
let timeString = String(format: "%02d:%02d", minutes, seconds)
NSLog(timeString)
Swift 2 version
extension NSTimeInterval {
func toMM_SS() -> String {
let interval = self
let componentFormatter = NSDateComponentsFormatter()
componentFormatter.unitsStyle = .Positional
componentFormatter.zeroFormattingBehavior = .Pad
componentFormatter.allowedUnits = [.Minute, .Second]
return componentFormatter.stringFromTimeInterval(interval) ?? ""
}
}
let duration = 326.4.toMM_SS()
print(duration) //"5:26"

converting an float to NSDate

I want to convert a float to a NSDate
I converted a NSDate into a float using this:
// Turn the date into Integers
NSCalendar *calendar= [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSCalendarUnit unitFlags = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *dateComponents = [calendar components:unitFlags fromDate:nsdate_wakeTime];
NSInteger hour = [dateComponents hour];
NSInteger min = [dateComponents minute];
//Convert the time in 24:60 to x.x format.
float myTime = hour + min/60;
after some math stuff I do on the mytime variable i get a bunch of other times in the same float format.
How do I turn a float into a NSDate?
Thanks!
If you convert the time you've computed to seconds (so, mytime * 60), then you can use dateWithTimeIntervalSinceReferenceDate: to get back to an NSDate. From the math you are doing, it looks like the referenced date here would be 00:00 for the day in question. As Jason mentioned though, there's probably a better way to do what you are trying to accomplish.
Also, you need to change your "myTime" computation to dividing by 60.0 if you actually want the minutes; your sample code is dividing an integer value less than 60 by the integer value 60, which will always be 0.