Swift: String to ints without special function - swift

I'm searching for a build-in function for converting a string like
"01092030" (format "[[dd]hh]mmss") to four individual integers.
dd => 01
hh => 09
mm => 20
ss => 30
dd and hh are optional. If missing they should be 0. I could do it by by developing a special function, but I like to use something like a regular expression.

I don't see the point of your "build-in function" or "regular expression" requirements, but whatever...
let s = "092030" as NSString
let pattern = "\\d\\d"
let reg = try! NSRegularExpression(pattern: pattern, options: [])
let matches = reg.matchesInString(s as String, options: [], range: NSMakeRange(0, s.length))
var result = matches.map {s.substringWithRange($0.range)}
while result.count < 4 {
result.insert("0", atIndex: 0)
}
// result is: ["0", "09", "20", "30"]
I'm also a little unclear on your output requirements. On the one hand, you say you want four "individual integers". But "09" is not an integer; it's a string representing an integer. So it seems to me that you actually want strings. If so, then result is the desired result. If not, then you need one more step:
let result2 = result.map{Int($0)!}
// result2 is: [0, 9, 20, 30]

Since the string contains date components you could alternatively use NSDateFormatter
let string = "01092030"
let dateFormatter = NSDateFormatter()
let dayMissing = string.characters.count < 8
let hourMissing = string.characters.count < 6
dateFormatter.dateFormat = hourMissing ? "mmss" : dayMissing ? "HHmmss" : "ddHHmmss"
let date = dateFormatter.dateFromString(string)!
let components = NSCalendar.currentCalendar().components([.Day, .Hour, .Minute, .Second], fromDate: date)
let second = components.second
let minute = components.minute
let hour = components.hour
let day = dayMissing ? 0 : components.day

Related

DateIntervalFormatter: string of format "m:ss"

I'd like to get a string of format "m:ss" out of two dates.
E.g.: "0:27" for 27 seconds difference and "1:30" for 90 seconds difference between dates.
Here's the code I'm using:
import Foundation
let formatter = DateIntervalFormatter()
formatter.dateStyle = .none
formatter.timeStyle = .none
formatter.dateTemplate = "m:ss"
let startDate = Date()
let endDate = Date(timeInterval: 1, since: startDate)
let outputString = formatter.string(from: startDate, to: endDate)
print(outputString) //16:12 – 16:13 ???
// This is correct, but it doesn't actually calculate the interval.
But I'm getting just two dates printed out with a dash.
How can I actually make the DateIntervalFormatter to calculate the difference as I want?
The code is almost 1:1 sample from the Apple documentation but with the custom dateTemplate: https://developer.apple.com/documentation/foundation/dateintervalformatter
It seems that you actually want DateComponentsFormatter
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
formatter.zeroFormattingBehavior = .pad
let startDate = Date()
let endDate = Date(timeInterval: 129, since: startDate)
let outputString = formatter.string(from: startDate, to: endDate)!
print(outputString)
to remove the leading zero if the minutes are < 10 you could use Regular Expression
print(outputString.replacingOccurrences(of: "^0(\\d)", with: "$1", options: .regularExpression))
I created this solution which doesn't involve the DateIntervalFormatter:
import Foundation
let minutes = 2
let seconds = 9
let formatted = String(format: "%01d:%02d", minutes, seconds)
print(formatted) // 2:09
Looks like what DateIntervalFormatter does is just applying a standard Date->String conversion to both of the dates and adds a dash between them.

Convert decimal time to hours and minutes in Swift

I am new to programming and this is my first program and question. I'm trying to write a function which will simply convert decimal time to Hours & Minutes. I'm removing the hours and multiplying the decimal minutes by 60 and adding the two back together as a string. I need to use this facility a couple of times in my program hence the function. The calculation which uses this function is straightforward but I'm getting odd results. If I maintain 'plannedStartFuel' as 450 and adjust 'minLandAllowance' I get the following results,
185 returns 1:28
182 returns 1:29
181 returns 1:30
180 returns 2:30
179 returns 2:30
175 returns 2:32
The correct answers are the 1:00 figures. I don't understand why the program seems to add an hour to the results at the 180 point. I'm sure there are are far better ways of completing this calculation than I've used, but if you can help I'd be grateful to know which part is causing the error and why. What have I tried?...everything! If you pitch your answer at a 7 year old I may have a chance of understanding. Thank you.
import UIKit
import Foundation
func decimalHoursConv (hours : Double) -> (_hrs:String, mins:String) {
let remainder = hours.truncatingRemainder(dividingBy: 1) * 60
let mins = (String(format: "%.0f", remainder))
let hrs = (String(format: "%.0f", hours))
return (hrs, mins)
}
var plannedStartFuel = Double (0)
var minLandAllowance = Double (0)
var flyingTimeToMLA = Double(0)
plannedStartFuel = 450
minLandAllowance = 180
flyingTimeToMLA = ((plannedStartFuel - minLandAllowance) / 3)/60
let MLAtime = (decimalHoursConv(hours: flyingTimeToMLA))
print ("Flight Time To MLA =", MLAtime.0,"hrs",MLAtime.1,"mins")
I might advise not bothering to calculate hours and minutes at all, but rather let DateComponentsFormatter do this, creating the final string for you.
For example:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.hour, .minute]
return formatter
}()
Then supply this formatter the elapsed time measured in seconds (a TimeInterval, which is just an alias for Double):
let remaining: TimeInterval = 90 * 60 // e.g. 90 minutes represented in seconds
if let result = formatter.string(from: remaining) {
print(result)
}
On a English speaking device, that will produce:
1 hour, 30 minutes
The virtue of this approach is that not only does it get you out of the business of manually calculating hours and minutes yourself, but also that the result is easily localized. So, if and when you get around to localizing your app, this string will be localized automatically for you, too, with no further work on your part. For example, if you add German to your app localizations, then the US user will still see the above, but on a German device, it will produce:
1 Stunde und 30 Minuten
If you want it to say how much time is remaining, set includesTimeRemainingPhrase:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.includesTimeRemainingPhrase = true
formatter.allowedUnits = [.hour, .minute]
return formatter
}()
That will produce:
1 hour, 30 minutes remaining
If you want a “hh:mm” sort of representation:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.zeroFormattingBehavior = .pad
formatter.allowedUnits = [.hour, .minute]
return formatter
}()
Will produce:
01:30
Bottom line, if you really want to calculate minutes and seconds, feel free, but if it’s solely to create a string representation, let the DateComponentFormatter do this for you.
EDIT
I realize you wanted to know what did not work with your method.
It's a matter of rounding, try roundind hours before passing it to String(format:) :
func decimalHoursConv (hours : Double) -> (_hrs:String, mins:String) {
let remainder = hours.truncatingRemainder(dividingBy: 1) * 60
let mins = (String(format: "%.0f", remainder))
let hours = hours.rounded(.towardZero)
let hrs = (String(format: "%.0f", hours))
return (hrs, mins)
}
it gives :
var value = (450.0-185.0)/3
decimalHoursConv(hours: value/60) // (_hrs "1", mins "28")
value = (450.0-182.0)/3
decimalHoursConv(hours: value/60) // (_hrs "1", mins "29")
value = (450.0-181.0)/3
decimalHoursConv(hours: value/60) // (_hrs "1", mins "30")
value = (450.0-180.0)/3
decimalHoursConv(hours: value/60) // (_hrs "1", mins "30")
value = (450.0-179.0)/3
decimalHoursConv(hours: value/60) // (_hrs "1", mins "30")
value = (450.0-175.0)/3
decimalHoursConv(hours: value/60) // (_hrs "1", mins "32")
BUT Still
If you're using Swift you should use Measurement
func convertToHoursAndMinutes(_ value: Double) -> DateComponents {
let unitMeasurement = Measurement(value: value, unit: UnitDuration.minutes)
let hours = unitMeasurement.converted(to: .hours).value
let decimalPart = hours.truncatingRemainder(dividingBy: 1)
let decimalPartMeasurement = Measurement(value: decimalPart, unit: UnitDuration.hours)
let decimalPartMeasurementInMinutes = decimalPartMeasurement.converted(to: .minutes)
let minutes = decimalPartMeasurementInMinutes.value.rounded(.toNearestOrEven)
return DateComponents(hour: Int(hours), minute: Int(minutes))
}
usage :
var value = (450.0-185.0)/3 // 88.33333333333333
convertToHoursAndMinutes(value) // hour: 1 minute: 28 isLeapMonth: false
value = (450.0-182.0)/3 // 89.33333333333333
convertToHoursAndMinutes(value) // hour: 1 minute: 29 isLeapMonth: false
value = (450.0-181.0)/3 // 89.66666666666667
convertToHoursAndMinutes(value) // hour: 1 minute: 30 isLeapMonth: false
value = (450.0-180.0)/3 // 90
convertToHoursAndMinutes(value) // hour: 1 minute: 30 isLeapMonth: false
value = (450.0-179.0)/3 // 90.33333333333333
convertToHoursAndMinutes(value) // hour: 1 minute: 30 isLeapMonth: false
value = (450.0-175.0)/3 // 91.66666666666667
convertToHoursAndMinutes(value) // hour: 1 minute: 32 isLeapMonth: false
Note that you can always use a tuple instead of DateComponents if you prefer.
String formatter rounds up.
You can use .rounded(.down) on Doubles to round them down. (or with other rules you need)
let number = (179.0/60.0) // 2.983333333333333
String(format: "%.0f", number) // returns 3
number.rounded(.up) // returns 3
number.rounded(.down) // returns 2
First you should structure your data. Next you don't need to format your value as a Double if you are not gonna display fractions. So you can simply convert your double to integer.
struct FlightPlan {
let plannedStartFuel: Double
let minimumLandAllowance: Double
}
extension FlightPlan {
var mlaTime: (hours: Int, minutes: Int) {
let hours = (plannedStartFuel - minimumLandAllowance) / 180
return (Int(hours), Int(modf(hours).1 * 60))
}
}
And you should use DateComponentsFormatter when displaying time to the user:
extension Formatter {
static let time: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.calendar?.locale = .init(identifier: "en_US_POSIX")
formatter.unitsStyle = .brief
formatter.allowedUnits = [.hour,.minute]
return formatter
}()
}
extension FlightPlan {
var mlaTimeDescrition: String {
return "Flight Time To MLA = " + Formatter.time.string(from: .init(hour: mlaTime.hours, minute: mlaTime.minutes))!
}
}
let flightPlan = FlightPlan(plannedStartFuel: 450,
minimumLandAllowance: 180)
flightPlan.mlaTime // (hours 1, minutes 30)
flightPlan.mlaTime.hours // 1
flightPlan.mlaTime.minutes // 30
flightPlan.mlaTimeDescrition // "Flight Time To MLA = 1hr 30min"

Get Date from week number in Swift 3

I have myYear and myWeek strings comes from apis , I want to convert them to date string like YYYY-mm-dd how can I do it in swift 3 ? my strings under below.
let myYear = "2017"
let myWeek = "8"
You just have to convert the strings into Ints, then generate a DateComponents object from them, get a Date from the DateComponents through a Calendar and finally use a DateFormatter to get the expected String representation of the date.
Bear in mind that the Date object will represent the first second of the week of that year and hence the String representation will correspond to the first day of that week.
let yearString = "2017"
let weekOfYearString = "8"
guard let year = Int(yearString), let weekOfYear = Int(weekOfYearString) else {return}
let components = DateComponents(weekOfYear: weekOfYear, yearForWeekOfYear: year)
guard let date = Calendar.current.date(from: components) else {return}
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd"
let outputDate = df.string(from: date) //2017-02-19

Using DateFormatter to parse an esoteric date string

I would like to use Foundation's DateFormatter to parse a datestring of the rather weird format /Date(1488335133000+0100)/ (representing 2017-03-01 03:25:33). As far as I can tell this describes the date as milliseconds since 1970 with a timezone of GMT+1 specified as well.
I can't however find a way to specify a format string for milliseconds or seconds as a unix timestamp. Is this even possible?
In case that's not possible, what would be the best option for parsing this date correctly? I'm currently resorting to picking apart the string until I have the milliseconds and timezone, dividing the milliseconds by 1000 and creating a new date object via Date(timeIntervalSince1970: seconds). Not quite sure how the timezone is supposed to play into this though.
DateFormatter can't handle this. Use NSRegularExpression to pick apart the components:
let str = "/Date(1488335133000+0100)/"
let regex = try! NSRegularExpression(pattern: "/Date\\((\\d+)(\\+|-)(\\d{2})(\\d{2})\\)/", options: [])
if let match = regex.firstMatch(in: str, options: [], range: NSMakeRange(0, str.characters.count)) {
let nsstr = str as NSString
let millisecond = Double(nsstr.substring(with: match.rangeAt(1)))!
let sign = nsstr.substring(with: match.rangeAt(2))
let hour = Double(nsstr.substring(with: match.rangeAt(3)))!
let minute = Double(nsstr.substring(with: match.rangeAt(4)))!
let offset = (sign == "+" ? 1 : -1) * (hour * 3600.0 + minute * 60.0)
let date = Date(timeIntervalSince1970: millisecond / 1000 + offset)
print(date)
}

Constructing NSDate using DateComponent gives inconsistent results

I've created an extension for NSDate which removes the time component to allow equality checks for NSDate based on date alone. I have achieved this by taking the original NSDate object, obtaining the day, month and year using the DateComponent class and then constructing a new NSDate using the information obtained. Although the NSDate objects obtained look correct when printed to the console (i.e. timestamp is 00:00:00) and using the NSDate.compare function on two identical dates returns NSComparisonResult.OrderedSame, if you deconstruct them using DateComponent once more, some of them have the hour set to 1. This appears to be a random event with this error being present about 55% of the time. Forcing the hour, minute and second properties of DateComponent to zero before constructing the new NSDate rather than assuming they will default to these values does not rectify the situation. Ensuring the timezone is set helps a little but again does not fix it.
I am guessing there may be a rounding error somewhere (possibly in my test code), I've fluffed the conversion or there is a Swift bug but would appreciate comments. Code and output from a unit test below.
Code as follows:
extension NSDate {
// creates a NSDate object with time set to 00:00:00 which allows equality checks on dates alone
var asDateOnly: NSDate {
get {
let userCalendar = NSCalendar.currentCalendar()
let dayMonthYearUnits: NSCalendarUnit = .CalendarUnitDay | .CalendarUnitMonth | .CalendarUnitYear
var dateComponents = userCalendar.components(dayMonthYearUnits, fromDate: self)
dateComponents.timeZone = NSTimeZone(name: "GMT")
// dateComponents.hour = 0
// dateComponents.minute = 0
// dateComponents.second = 0
let result = userCalendar.dateFromComponents(dateComponents)!
return result
}
}
Test func:
func testRemovingTimeComponentFromRandomNSDateObjectsAlwaysResultsInNSDateSetToMidnight() {
var dates = [NSDate]()
let dateRange = NSDate.timeIntervalSinceReferenceDate()
for var i = 0; i < 30; i++ {
let randomTimeInterval = Double(arc4random_uniform(UInt32(dateRange)))
let date = NSDate(timeIntervalSinceReferenceDate: randomTimeInterval).asDateOnly
let dateStrippedOfTime = date.asDateOnly
// get the hour, minute and second components from the stripped date
let userCalendar = NSCalendar.currentCalendar()
var hourMinuteSecondUnits: NSCalendarUnit = .CalendarUnitHour | .CalendarUnitMinute | .CalendarUnitSecond
var dateComponents = userCalendar.components(hourMinuteSecondUnits, fromDate: dateStrippedOfTime)
dateComponents.timeZone = NSTimeZone(name: "GMT")
XCTAssertTrue((dateComponents.hour == 0) && (dateComponents.minute == 0) && (dateComponents.second == 0), "Time components were not set to zero - \nNSDate: \(date) \nIndex: \(i) H: \(dateComponents.hour) M: \(dateComponents.minute) S: \(dateComponents.second)")
}
}
Output:
testRemovingTimeComponentFromRandomNSDateObjectsAlwaysResultsInNSDateSetToMidnight] : XCTAssertTrue failed - Time components were not set to zero -
NSDate: 2009-06-19 00:00:00 +0000
Index: 29 H: 1 M: 0 S: 0
I am sure that your test dates you created randomly will contain dates that live in DST (Daylight Saving Time) hence the 1 hour offset — indeed a clock would show 0:00.
Your code is anyway overly complicated and not timezone aware, as you overwrite it.
Preparation: create to dates on the same day with 5 hours apart.
var d1 = NSDate()
let cal = NSCalendar.currentCalendar()
d1 = cal.dateBySettingUnit(NSCalendarUnit.CalendarUnitHour, value: 12, ofDate: d1, options: nil)!
d1 = cal.dateBySettingUnit(NSCalendarUnit.CalendarUnitMinute, value: 0, ofDate: d1, options: nil)!
d1 is noon at users location today.
let comps = NSDateComponents()
comps.hour = 5;
var d2 = cal.dateByAddingComponents(comps, toDate: d1, options: nil)!
d2 five hours later
Comparison: This comparison will yield equal, as the dates are on the same day
let b = cal.compareDate(d1, toDate: d2, toUnitGranularity: NSCalendarUnit.CalendarUnitDay)
if b == .OrderedSame {
println("equal")
} else {
println("not equal")
}
The following will yield non equal, as the dates are not in the same hour
let b = cal.compareDate(d1, toDate: d2, toUnitGranularity: NSCalendarUnit.CalendarUnitHour)
if b == .OrderedSame {
println("equal")
} else {
println("not equal")
}
Display the dates with a date formatter, as it will take DST and timezones in account.