Displaying String based on 3 int values - ios

I'm trying to display a string in this format
x day, y hr, z min
where x, y, z are Int values from user and user is required to send in at least 5 min if day and hr are 0.
This is my function where I handle displaying user input
func pickerTimeShow(day: Int, hour: Int, minutes: Int) -> String {
if day > 0 {
return "\(day) day, \(hour) hr, \(minutes) min"
}
if hour > 0 {
return "\(hour) hr, \(minutes) min"
}
return "\(minutes) min"
}
However, if user sends pickerTimeShow(day: 0, hour: 2, minutes: 0), my string will show
2 hr, 0 min
which is not idea because it supposes to just show
2 hr
In addition, if user sends pickerTimeShow(day: 5, hour: 0, minutes: 5), my string will show
5 day, 0 hr, 5 min
but I just want
5 day, 5 min
Do you have a simple solution to handle this case? I really can't think of anything else besides adding more if statement to check which makes my function really long. Thank you so much.

Here is one way to do it. Put the pieces into an array and then join them at the end:
func pickerTimeShow(day: Int, hour: Int, minutes: Int) -> String {
var result = [String]()
if day > 0 {
result.append("\(day) day")
}
if hour > 0 {
result.append("\(hour) hr")
}
if minutes > 0 {
result.append("\(minutes) min")
}
if result.isEmpty {
// decide what to return in the case that all are zero
return "0 min"
} else {
return result.joined(separator: ", ")
}
}
Tests:
pickerTimeShow(day: 0, hour: 0, minutes: 0) // "0 min"
pickerTimeShow(day: 5, hour: 0, minutes: 0) // "5 day"
pickerTimeShow(day: 0, hour: 6, minutes: 0) // "6 hr"
pickerTimeShow(day: 0, hour: 0, minutes: 7) // "7 min"
pickerTimeShow(day: 5, hour: 6, minutes: 0) // "5 day, 6 hr"
pickerTimeShow(day: 0, hour: 6, minutes: 7) // "6 hr, 7 min"
pickerTimeShow(day: 5, hour: 0, minutes: 7) // "5 day, 7 min"
pickerTimeShow(day: 5, hour: 6, minutes: 7) // "5 day, 6 hr, 7 min"

Alternatively – if you can live with 5 days rather than 5 day – this is a solution using DateComponentsFormatter
func pickerTimeShow(day: Int, hour: Int, minutes: Int) -> String? {
let dateComponents = DateComponents(day: day, hour: hour, minute: minutes)
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .short
formatter.allowedUnits = [.day, .hour, .minute]
return formatter.string(from: dateComponents)
}
The function returns nil if the input is out-of-order.

Related

How to use DateComponents to represent the extra 1 hour period at the end of day light saving?

Usually, during end of day light saving, we will be gaining extra 1 hours.
Take Tehran timezone as an example.
During 22 September 2021, Tehran will backward by 1 hour from 00:00
AM, to 11:00 PM.
I wrote the following code to demonstrate such.
import UIKit
func date(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) -> Date {
var dateComponents = DateComponents()
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = second
let date = Calendar.current.date(from: dateComponents)!
return date
}
// During 22 September 2021, Tehran will backward by 1 hour from 00:00 AM, to 11:00 PM.
let tehranTimeZone = TimeZone(identifier: "Asia/Tehran")!
let oldDefault = NSTimeZone.default
NSTimeZone.default = tehranTimeZone
defer {
NSTimeZone.default = oldDefault
}
let date1 = date(year: 2021, month: 09, day: 21, hour: 23, minute: 59, second: 59)
let date2 = date(year: 2021, month: 09, day: 22, hour: 00, minute: 00, second: 00)
let date3 = date(year: 2021, month: 09, day: 22, hour: 00, minute: 00, second: 01)
// STEP 1: 2021 Sep 21 23:59:59 => 1632252599.0, Tuesday, September 21, 2021 at 11:59:59 PM Iran Daylight Time
print("STEP 1: 2021 Sep 21 23:59:59 => \(date1.timeIntervalSince1970), \(date1.description(with: .current))")
// STEP 2: 2021 Sep 22 00:00:00 => 1632256200.0, Wednesday, September 22, 2021 at 12:00:00 AM Iran Standard Time
print("STEP 2: 2021 Sep 22 00:00:00 => \(date2.timeIntervalSince1970), \(date2.description(with: .current))")
// STEP 3: 2021 Sep 22 00:00:01 => 1632256201.0, Wednesday, September 22, 2021 at 12:00:01 AM Iran Standard Time
print("STEP 3: 2021 Sep 22 00:00:01 => \(date3.timeIntervalSince1970), \(date3.description(with: .current))")
From STEP 1 transits to STEP 2, instead for their timeIntervalSince1970 different by +1 seconds, their difference are +3601 seconds, due to the extra 1 hour gain.
Now, my question is, how can we use DateComponents to represent the extra 1 hour period at the end of day light saving?
In another, how can I use DateComponents to generate a Date which is capable to print the following?
2021 Sep 21 23:00:00 => 1632252600.0, Tuesday, September 21, 2021 at 11:00:00 PM Iran Standard Time
Now, we understand that, in Tehran, during 2021 Sept 21, there are 2 type of 23:00:00 time
23:00:00 Iran Daylight time (Epoch is 1632249000)
23:00:00 Iran Standard time (Epoch is 1632252600)
23:00:00 Iran Daylight time (Epoch is 1632249000)
I can represent the above using
let date = date(year: 2021, month: 09, day: 21, hour: 23, minute: 00, second: 00)
23:00:00 Iran Standard time (Epoch is 1632252600)
I have no idea how to represent the above. As, I do not find a way in DateComponents, to enable us to specific whether the local time is belong to standard time, or daylight time.
DateComponents do not have a time zone. The time zone comes into it when you convert DateComponents to a Date, using the call Calendar.date(from:). It's the calendar's time zone that determines how those DateComponents are converted to a Date.
Instead of using Calendar.current, create a custom calendar and set it to the IRST time zone. (I couldn't figure out the time zone for Iran Daylight time. I would have expected it to have the abbreviation "IRDT", but that doesn't work.)
Let's say we have a calendar irstCalendar that's set to Iran Standard Time ("IRST").
If you use irstCalendar.date(from: dateComponents) you'll always get the Date based on standard time.
Consider this code:
func date(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int, calendar: Calendar = Calendar.current) -> Date {
var dateComponents = DateComponents()
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = second
let date = calendar.date(from: dateComponents)!
return date
}
guard let tehranStandardTimeZone = TimeZone(abbreviation: "IRST") else {
fatalError("Can't create time zones")
}
var tehranSTCalendar = Calendar(identifier: .gregorian)
tehranSTCalendar.timeZone = tehranStandardTimeZone
let tehranDateFormatter = DateFormatter()
tehranDateFormatter.dateStyle = .medium
tehranDateFormatter.timeStyle = .medium
tehranDateFormatter.timeZone = tehranStandardTimeZone
let date1 = date(year: 2021, month: 09, day: 21, hour: 23, minute: 59, second: 59, calendar: tehranSTCalendar)
let date2 = date(year: 2021, month: 09, day: 22, hour: 00, minute: 00, second: 00, calendar: tehranSTCalendar)
let date3 = date(year: 2021, month: 09, day: 22, hour: 00, minute: 00, second: 01, calendar: tehranSTCalendar)
print("STEP 1: 2021 Sep 21 23:59:59 => \(date1.timeIntervalSince1970), \(tehranDateFormatter.string(from:date1))")
print("STEP 2: 2021 Sep 22 00:00:00 => \(date2.timeIntervalSince1970), \(tehranDateFormatter.string(from:date2))")
print("STEP 3: 2021 Sep 22 00:00:01 => \(date3.timeIntervalSince1970), \(tehranDateFormatter.string(from:date3))")
That outputs:
STEP 1: 2021 Sep 21 23:59:59 => 1632252599.0, Sep 21, 2021 at 11:59:59 PM
STEP 2: 2021 Sep 22 00:00:00 => 1632256200.0, Sep 22, 2021 at 12:00:00 AM
STEP 3: 2021 Sep 22 00:00:01 => 1632256201.0, Sep 22, 2021 at 12:00:01 AM

Elegant way to show an array list of int

Hi everyone I am working with a LazyVGrid on my SwiftUI project for displaying a list of times ...
This is the array of hours
let hoursList = [09, 10, 10, 11, 11, 12, 12, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18]
let columns = Array(repeating: GridItem(), count: 4)
LazyVGrid(columns: columns, alignment: .center, spacing: 10) {
ForEach(viewModel.hoursList) { hours in
Text("\(hours)")
}
///////////
// more elegant way to get the times as in the array?
//////////
}
As you can see in the array there are times that repeat for example 10 - 10, 11 - 11
Is there a more elegant way to get an array like the one above but without specifying every number? instead of directly using 'hourList' can I reach my goal using a more elegant way?
Hour & Minute should Convert into Number System, so
HH:mm from Clock will be (HH).(mm/60) in Numeral
02:00 is 2.0
06:30 is 6.5
08:45 is 8.75
15:55 is 15.916666...
extension Double {
var minute: Int { Int(self * 60) - Int(self)*60 }
}
let hoursList = [9.5, 10.0, 10.5, 12, 12.5, 14.5, 15, 16, 17, 17.75, 18, 18.25]
let columns = Array(repeating: GridItem(), count: 4)
LazyVGrid(columns: columns, alignment: .center, spacing: 10) {
ForEach(viewModel.hoursList) { hour in
Text(String(format: "%d : %.2d", Int(hour), hour.minute))
}
}

Wrong date from components

So this is interesting:
po Calendar.default.date(from: DateComponents(year: 2022, month: 1, hour: 16, minute: 1, second: 1, weekday: 1, weekOfMonth: 1))
▿ Optional<Date>
▿ some : 2021-12-26 23:01:01 +0000
- timeIntervalSinceReferenceDate : 662252461.0
I'm expecting January 1st 2022, but I'm getting December 26th 2021? Why is it doing this? Am I doing something wrong?
It's because the date components you are providing are contradictory. You have weekDay 1 (which will probably be a Sunday or Monday, depending on your locale) but the 1 jan 2022 is a Saturday.
(you also used Calendar.default when I think you meant Calendar.current?)
If you take out the weekDay term you will get the correct answer:
Calendar.current.date(from: DateComponents(year: 2022, month: 1, hour: 16, minute: 1, second: 1, weekOfMonth: 1))
// "Jan 1, 2022 at 4:01 PM"
You could also remove the weekOfMonth term as it is superfluous when you are specifying the actual date.

Split last two hours in 10 minutes slices

I am trying to get every 10 minutes of the last hour.
For example, now is 15:46:41
I want [15:40:00, 15:30:00, 15:20:00, 15:10:00, 15:00:00, 14:50:00, 14:40:00, 14:30:00, 14:20:00, 14:10:00, 14:00:00, 13:50:00, 13:40:00]
let calendar = Calendar.current
let now = Date()
var components = DateComponents()
components.hour = -2
if let early = calendar.date(byAdding: components, to: now) {
let nowMin = calendar.component(.minute, from: early)
let diff = 10 - (nowMin % 10)
components.minute = diff
var minutes: [Int] = []
for _ in 0...13 {
// I cant figure out what should I do next.
}
print(minutes)
}
You can get now's minute, get the remainder of this value divided by ten and subtract it from that value. This way you get the last tenth hour minute, then you just need to set it with the same hour component to now to find out the first element of your array. Next you can fill the rest of dates subtracting 10 minutes times the element position from the start date. Try like this:
Xcode 11 • Swift 5.1 (for older versions just add the return statement as usual)
extension Date {
var hour: Int { Calendar.current.component(.hour, from: self) }
var minute: Int { Calendar.current.component(.minute, from: self) }
var previousHourTenth: Date { Calendar.current.date(bySettingHour: hour, minute: minute - minute % 10, second: 0, of: self)! }
func lastNthHourTenth(n: Int) -> [Date] { (0..<n).map { Calendar.current.date(byAdding: .minute, value: -10*$0, to: previousHourTenth)! } }
}
Playground testing
Date() // "Sep 25, 2019 at 10:19 AM"
Date().previousHourTenth // "Sep 25, 2019 at 10:10 AM"
Date().lastNthHourTenth(n: 13) // "Sep 25, 2019 at 10:10 AM", "Sep 25, 2019 at 10:00 AM", "Sep 25, 2019 at 9:50 AM", "Sep 25, 2019 at 9:40 AM", "Sep 25, 2019 at 9:30 AM", "Sep 25, 2019 at 9:20 AM", "Sep 25, 2019 at 9:10 AM", "Sep 25, 2019 at 9:00 AM", "Sep 25, 2019 at 8:50 AM", "Sep 25, 2019 at 8:40 AM", "Sep 25, 2019 at 8:30 AM", "Sep 25, 2019 at 8:20 AM", "Sep 25, 2019 at 8:10 AM"]
Now you just need to use DateFormatter to display those dates as needed to the user.

Swift dates are equivalent under 30 nanoseconds

I noticed that when comparing two instances of a Swift Date with ==, they qualify as the same date when the difference in DateComponents.nanoseconds is less than 30. For example:
let calendar = Calendar(identifier: .gregorian)
let startComps = DateComponents(year: 2017, month: 1, day: 1, hour: 0, minute: 0, second: 0, nanosecond: 0)
let endComps = DateComponents(year: 2017, month: 1, day: 1, hour: 0, minute: 0, second: 0, nanosecond: 29)
let startDate = calendar.date(from: startComps)!
let endDate = calendar.date(from: endComps)!
print(startDate == endDate)
//prints true, changing 29 to 30 prints false
The behavior is the same if comparing with startDate.compare(endDate) == .orderedSame. I couldn't find any mention of this in the docs or headers. Is there a logical reason for 30 nanoseconds to be the cutoff for equality?
Dates are represented internally as a Double. Doubles have a limited precision. Your first date there has an internal value of 504950400.000000000000000 (when printed at 15 decimal places). The next higher representable value is 504950400.000000059604645. It just so happens that your end date, if you set the nanoseconds to 30, is 504950400.000000059604645 (which actually corresponds to a nanoseconds value of 59).
Which is to say, in January of 2017, Date can only distinguish between 59-nanosecond intervals. Meanwhile back in January of 2001, Date can distinguish every single nanosecond. And in 2027 it will distinguish between 119-nanosecond intervals.

Resources