Restricting hours and minutes in DatePicker- swift? - swift

hi guys im trying to create a time picker which hours are only from (2pm - 9pm) and minutes are incremented by 30 this is the picture of timePicker that i want to create
here is my code
func creatTimePicker() {
timePicker.datePickerMode = .time
timePicker.minuteInterval = 30
let timeformatter = DateFormatter()
timeformatter.dateFormat = "HH:mm"
let min = timeformatter.date(from: "5:00")
let max = timeformatter.date(from: "9:00")
timePicker.minimumDate = min
timePicker.maximumDate = max
self.contentView.addSubview(timePicker)
}
the problem is hour starts 1-12 and minute starts 00-59 what is the problem? thank you

The problem is that you need to set the date picker date property to match the minimum date as well. You need also to set the date formatter default date to today and don't forget to set the locale to "en_US_POSIX" when parsing your date string time:
extension Formatter {
static let time: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = .init(identifier: "em_US_POSIX")
formatter.dateFormat = "HH:mm"
return formatter
}()
}
let timePicker = UIDatePicker()
func creatTimePicker() {
Formatter.time.defaultDate = Calendar.current.startOfDay(for: Date())
let minimumDate = Formatter.time.date(from: "14:00")!
let maximumDate = Formatter.time.date(from: "21:00")!
timePicker.date = minimumDate
timePicker.datePickerMode = .time
timePicker.minuteInterval = 30
timePicker.minimumDate = minimumDate
timePicker.maximumDate = maximumDate
self.contentView.addSubview(timePicker)
}

You can use Calendar to constraint the time range for UIDatePicker()
let picker = UIDatePicker()
picker.datePickerMode = .time
let minDate = Calendar.current.date(bySettingHour: 14, minute: 0, second: 0, of: Date()) // 2PM
let maxDate = Calendar.current.date(bySettingHour: 21, minute: 0, second: 0, of: Date()) // 9PM
picker.minimumDate = minDate
picker.maximumDate = maxDate
This won't let user select time outside the specified time range.
In case you want to show only these hours
You can use UIPickerView
Example:
let picker = UIPickerView()
picker.dataSource = self
picker.delegate = self
let hoursRange = Array(5...9)
// Data source
func numberOfComponents(in: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
print("Com :\(component)")
if component == 0 {
return hoursRange.count
}
return 1 // For the PM indicator
}
// Delegate
func pickerView(_ pickerView: UIPickerView,
titleForRow row: Int,
forComponent component: Int) -> String? {
switch component {
case 0:
return "\(hoursRange[row])"
case 1:
return "AM"
default:
return nil
}
}

Related

DateTimePicker not displaying the right date

I'm using a DateTimePicker that contains a Start Date and Time. Although the time updates properly, the date does not update based on the scrollview. Any idea what I can change to make it work?
Here's the code:
class DateTimePicker: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
// Reference from https://stackoverflow.com/questions/40878547/is-it-possible-to-have-uidatepicker-work-with-start-and-end-time
var didSelectDates: ((_ start: Date) -> Void)?
private lazy var pickerView: UIPickerView = {
let pickerView = UIPickerView()
pickerView.delegate = self
pickerView.dataSource = self
pickerView.backgroundColor = .white
return pickerView
}()
private var days = [Date]()
private var startTimes = [Date]()
// private var endTimes = [Date]()
let dayFormatter = DateFormatter()
let timeFormatter = DateFormatter()
var inputView: UIView {
return pickerView
}
func setup() {
dayFormatter.dateFormat = "EE d MMM"
timeFormatter.timeStyle = .short
days = setDays()
startTimes = setStartTimes()
// endTimes = setEndTimes()
}
// MARK: - UIPickerViewDelegate & DateSource
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch component {
case 0:
return days.count
case 1:
return startTimes.count
// case 2:
// return endTimes.count
default:
return 0
}
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
var label: UILabel
if let view = view as? UILabel {
label = view
} else {
label = UILabel()
}
label.textColor = .black
label.textAlignment = .center
label.font = UIFont.systemFont(ofSize: 15)
var text = ""
switch component {
case 0:
text = getDayString(from: days[row])
case 1:
text = getTimeString(from: startTimes[row])
// case 2:
// text = getTimeString(from: endTimes[row])
default:
break
}
label.text = text
return label
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
let dayIndex = pickerView.selectedRow(inComponent: 0)
let startTimeIndex = pickerView.selectedRow(inComponent: 1)
// let endTimeIndex = pickerView.selectedRow(inComponent: 2)
guard days.indices.contains(dayIndex),
startTimes.indices.contains(startTimeIndex)
// endTimes.indices.contains(endTimeIndex)
else { return }
let startTime = startTimes[startTimeIndex]
// let endTime = endTimes[endTimeIndex]
didSelectDates?(startTime)
}
// MARK: - Private helpers
private func getDays(of date: Date) -> [Date] {
var dates = [Date]()
let calendar = Calendar.current
// first date
var currentDate = date
// adding 30 days to current date
let oneMonthFromNow = calendar.date(byAdding: .day, value: 30, to: currentDate)
// last date
let endDate = oneMonthFromNow
while currentDate <= endDate! {
dates.append(currentDate)
currentDate = calendar.date(byAdding: .day, value: 1, to: currentDate)!
}
return dates
}
private func getTimes(of date: Date) -> [Date] {
var times = [Date]()
var currentDate = date
currentDate = Calendar.current.date(bySetting: .hour, value: 7, of: currentDate)!
currentDate = Calendar.current.date(bySetting: .minute, value: 00, of: currentDate)!
let calendar = Calendar.current
let interval = 60
var nextDiff = interval - calendar.component(.minute, from: currentDate) % interval
var nextDate = calendar.date(byAdding: .minute, value: nextDiff, to: currentDate) ?? Date()
var hour = Calendar.current.component(.hour, from: nextDate)
while(hour < 23) {
times.append(nextDate)
nextDiff = interval - calendar.component(.minute, from: nextDate) % interval
nextDate = calendar.date(byAdding: .minute, value: nextDiff, to: nextDate) ?? Date()
hour = Calendar.current.component(.hour, from: nextDate)
}
return times
}
private func setDays() -> [Date] {
let today = Date()
return getDays(of: today)
}
private func setStartTimes() -> [Date] {
let today = Date()
return getTimes(of: today)
}
// private func setEndTimes() -> [Date] {
// let today = Date()
// return getTimes(of: today)
// }
private func getDayString(from: Date) -> String {
return dayFormatter.string(from: from)
}
private func getTimeString(from: Date) -> String {
return timeFormatter.string(from: from)
}
}
extension Date {
static func buildTimeRangeString(startDate: Date) -> String {
let dayFormatter = DateFormatter()
dayFormatter.dateFormat = "EEEE, MMM d"
let startTimeFormatter = DateFormatter()
startTimeFormatter.dateFormat = "h a"
// let endTimeFormatter = DateFormatter()
// endTimeFormatter.dateFormat = "h:mm a"
return String(format: "%#, %#",
dayFormatter.string(from: startDate),
startTimeFormatter.string(from: startDate))
// endTimeFormatter.string(from: endDate))
}
}
(Reference: https://www.youtube.com/watch?v=vZsJwsZ3iKQ)
In the main MessagesViewController, I have :
private lazy var dateTimePicker: DateTimePicker = {
let picker = DateTimePicker()
picker.setup()
picker.didSelectDates = {[weak self](startDate) in
let text = Date.buildTimeRangeString(startDate: startDate)
self?.actualDate = startDate
self?.label.text = text
dateText = text
}
return picker
}()

operation with hours using swift, sum and difference

On my project I need to work with hours and min.
I can't find to much info online.
here my issue:
let time1 = "22:00"
let time2 = "20:00"
Question 1:
how do I subtract the time1 - time2 ?
I'm expecting result = 02:00 h
i start writing some code, converting this time to date..
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm"
var time1d = formatter.date(from: time1)!
let time2d = formatter.date(from: time2)!
but now how do I subtract this two hours?
Question 2:
let time1 = "22:00"
let time2 = "20:00"
How do i sum time1 + time2 ? for example should give me result 42:00 hours
thanks for the help
This is not a Date. If you are only gonna work with hours and minutes and the input will always be a string properly formatted you should struct your data.
Create a Time structure
struct Time {
let hour: Int
let minute: Int
}
And a custom initializer. This assumes your string is always properly formatted 00:00:
extension Time {
init?(string: String) {
guard string.count == 5,
Array(string)[2] == ":",
let hour = Int(string.prefix(2)),
let minute = Int(string.suffix(2)),
0...59 ~= minute else {
return nil
}
self.hour = hour
self.minute = minute
}
}
For displaying your Time struct property you can conform it to CustomStringConvertible and provide a custom description
extension Time: CustomStringConvertible {
var description: String {
String(format: "%02d:%02d", hour, minute)
}
}
Regarding adding and subtracting you can make your Time struct conform to AdditiveArithmetic and implement the required operators:
extension Time: AdditiveArithmetic {
static func - (lhs: Time, rhs: Time) -> Time {
let minutes = lhs.minute - rhs.minute + lhs.hour * 60 - rhs.hour * 60
return .init(hour: minutes/60, minute: minutes%60)
}
static func + (lhs: Time, rhs: Time) -> Time {
let minutes = lhs.minute + rhs.minute + lhs.hour * 60 + rhs.hour * 60
return .init(hour: minutes/60, minute: minutes%60)
}
static var zero: Time { .init(hour: 0, minute: 0) }
}
Playground testing:
let time1 = Time(string: "22:00")!
let time2 = Time(string: "20:00")!
let time3 = time1-time2
print(time3)
let time4 = time1+time2
print(time4)
Those will print
02:00
42:00
First we create the calculateDifference function which uses dateComponents function provided by Swift.
let time1 = "22:00"
let time2 = "20:00"
func formattedTime(_ time: String) -> Date? {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "HH:mm"
return formatter.date(from: time)
}
func calculateDifference(_ from: String, _ to: String) -> (hour: Int, minutes: Int) {
guard let fromTime = formattedTime(from),
let toTime = formattedTime(to) else {
return (0,0)
}
let calendar = Calendar.current
let components = calendar.dateComponents([.hour, .minute], from: fromTime, to: toTime)
return (components.hour ?? 0, components.minute ?? 0)
}
Then we create the calculateSum which uses the calculateDifference internally to get the hours by supplying the default hour as 00:00
func calculateSum(_ time1: String, _ time2: String) -> (hour: Int, minutes: Int) {
let defaultTime = "00:00"
let calculatedTime1 = calculateDifference(defaultTime, time1)
let calcaultedTime2 = calculateDifference(defaultTime, time2)
return(calculatedTime1.hour + calcaultedTime2.hour,
calculatedTime1.minutes + calcaultedTime2.minutes)
}
Now if we run the below, we get the required results. Of course, some formatting will be required.
let difference = calculateDifference(time2, time1)
print("\(difference.hour):\(difference.minutes)")
let sum = calculateSum(time1, time2)
print("\(sum.hour):\(sum.minutes)")

Custom xAxis formatting in iOS Charts library

I'm using the following library to implement charts in my iOS app:
https://github.com/danielgindi/Charts
On the xAxis I would like to have a formatting similar to the one on the screenshot.
I would like to display the name of the month once, followed by days of that month (without month name), and when the months changes, display the name again.
Can someone point to the right direction?
Current code for xAxis configuration, which shows month and day for each entry:
xAxis.drawAxisLineEnabled = true
xAxis.drawGridLinesEnabled = false
xAxis.centerAxisLabelsEnabled = true
xAxis.drawLabelsEnabled = true
xAxis.axisLineColor = R.Color.Chart.Alpha.separator
xAxis.labelTextColor = R.Color.Chart.Alpha.content
xAxis.labelPosition = .bottom
xAxis.labelCount = 5
xAxis.valueFormatter = XAxisValueFormatter()
class XAxisValueFormatter: AxisValueFormatter {
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
let date = Date(timeIntervalSince1970: value)
return date.to(DateFormat.monthday)
}
}
You need to update your existing code as below.
Update your data binding for XAxis:
let chartFormatter = LineChartFormatter(labels: xValues)
let xAxis = XAxis()
xAxis.valueFormatter = chartFormatter
self.xAxis.valueFormatter = xAxis.valueFormatter
Update Value Formatter code:
private class LineChartFormatter: NSObject, IAxisValueFormatter {
var labels: [String] = []
let dateFormatter = DateFormatter()
let dateShortFormatter = DateFormatter()
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
if let date = dateFormatter.date(from:labels[Int(value)]) {
if value == 0 {
dateShortFormatter.dateFormat = "MMM dd"
return dateShortFormatter.string(from: date)
} else {
let prevDate = dateFormatter.date(from:labels[Int(value - 1)])
dateShortFormatter.dateFormat = "MMM"
if dateShortFormatter.string(from: date) != dateShortFormatter.string(from: prevDate!) {
dateShortFormatter.dateFormat = "MMM dd"
return dateShortFormatter.string(from: date)
} else {
dateShortFormatter.dateFormat = "dd"
return dateShortFormatter.string(from: date)
}
}
} else {
return labels[Int(value)]
}
}
init(labels: [String]) {
super.init()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
self.labels = labels
}}
By Above code change, you will achieve your Date formatting in XAxis.

How to get timezone offset as ±hh:mm?

I can get the offset seconds from GMT with this: TimeZone.current.secondsFromGMT().
However, how do I get the format as ±hh:mm?
Some integer arithmetic to obtain the offset in hours and
minutes:
let seconds = TimeZone.current.secondsFromGMT()
let hours = seconds/3600
let minutes = abs(seconds/60) % 60
Formatted printing:
let tz = String(format: "%+.2d:%.2d", hours, minutes)
print(tz) // "+01:00"
%.2d prints an integer with (at least) two decimal digits (and leading
zero if necessary). %+.2d is the same but with a leading + sign for
non-negative numbers.
Here is extension for getting timezone offset Difference and as ±hh:mm (Swift 4 | Swift 5 Code)
extension TimeZone {
func offsetFromUTC() -> String
{
let localTimeZoneFormatter = DateFormatter()
localTimeZoneFormatter.timeZone = self
localTimeZoneFormatter.dateFormat = "Z"
return localTimeZoneFormatter.string(from: Date())
}
func offsetInHours() -> String
{
let hours = secondsFromGMT()/3600
let minutes = abs(secondsFromGMT()/60) % 60
let tz_hr = String(format: "%+.2d:%.2d", hours, minutes) // "+hh:mm"
return tz_hr
}
}
Use like this
print(TimeZone.current.offsetFromUTC()) // output is +0530
print(TimeZone.current.offsetInHours()) // output is "+05:30"
If you can use Date()
func getCurrentTimezone() -> String {
let localTimeZoneFormatter = DateFormatter()
localTimeZoneFormatter.dateFormat = "ZZZZZ"
return localTimeZoneFormatter.string(from: Date())
}
Will return "+01:00" format
extension TimeZone {
func offsetFromUTC() -> String
{
let localTimeZoneFormatter = DateFormatter()
localTimeZoneFormatter.timeZone = self
localTimeZoneFormatter.dateFormat = "Z"
return localTimeZoneFormatter.string(from: Date())
}
func currentTimezoneOffset() -> String {
let timeZoneFormatter = DateFormatter()
timeZoneFormatter.dateFormat = "ZZZZZ"
return timeZoneFormatter.string(from: Date())
}
}
Use like this
print(TimeZone.current.offsetFromUTC()) // output is +0530
print(TimeZone.current.currentTimezoneOffset()) // output is "+05:30"
it working 100% in all countries according to timezone.
Swift 4 and above
extension TimeZone {
func timeZoneOffsetInHours() -> Int {
let seconds = secondsFromGMT()
let hours = seconds/3600
return hours
}
func timeZoneOffsetInMinutes() -> Int {
let seconds = secondsFromGMT()
let minutes = abs(seconds / 60)
return minutes
}
}
The accepted answer does not handle the case "-00:30" correctly since the "+/-" is only being determined from the hours, and not the minutes. I would set the sign based on a check of the initial seconds value. Alternatively you could use DateComponentsFormatter.
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .positional
formatter.zeroFormattingBehavior = .pad
let interval: TimeInterval = TimeInterval.init(abs(secondsOffset))
let offsetValue: String = formatter.string(from: interval)

Working out the start and end of a day. Swift

I have a function to work out the start and end of a week which works as expected. I want to implement another function which works out the start and end of a single day. I have the code below however I get the following error:
Type of expression is ambiguous without more context.
public class Date {
let dateFormatter = NSDateFormatter()
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
func calcStartAndEndDateForWeek(durationOccurance: Double) {
print("Calculating start and end for week")
let componentsWeek = calendar.components([.YearForWeekOfYear, .WeekOfYear], fromDate: date)
let startOfWeek = calendar.dateFromComponents(componentsWeek)!
print("start of Week = \(dateFormatter.stringFromDate(startOfWeek))")
let componentsWeekEnds = NSDateComponents()
componentsWeekEnds.weekOfYear = 1
let endOfWeek = calendar.dateByAddingComponents(componentsWeekEnds, toDate: startOfWeek, options: [])!
print("End of the week = \(dateFormatter.stringFromDate(endOfWeek))")
}
func calcStartAndEndDateForDay(durationOccurance: Double) {
print("Calculating start and end for day")
let componentsWeek = calendar.components([.Minutes, .Seconds], fromDate: date)
let startOfDay = calendar.dateFromComponents(componentsWeek)!
print("start day = \(dateFormatter.stringFromDate(startOfDay))")
}
init(){
dateFormatter.dateFormat = "dd-MM-yyyy"
}
}
We can create a more generic function using the methods on NSCalendar:
func rangeOfPeriod(period: NSCalendarUnit, date: NSDate) -> (NSDate, NSDate) {
let calendar = NSCalendar.currentCalendar()
var startDate: NSDate? = nil
// let's ask calendar for the start of the period
calendar.rangeOfUnit(period, startDate: &startDate, interval: nil, forDate: date)
// end of this period is the start of the next period
let endDate = calendar.dateByAddingUnit(period, value: 1, toDate: startDate!, options: [])
// you can subtract 1 second if you want to make "Feb 1 00:00:00" into "Jan 31 23:59:59"
// let endDate2 = calendar.dateByAddingUnit(.Second, value: -1, toDate: endDate!, options: [])
return (startDate!, endDate!)
}
Called as
print("\(rangeOfPeriod(.WeekOfYear, date: NSDate()))")
print("\(rangeOfPeriod(.Day, date: NSDate()))")
Putting it into your code:
public class Date {
let dateFormatter = NSDateFormatter()
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
func rangeOfPeriod(period: NSCalendarUnit) -> (NSDate, NSDate) {
var startDate: NSDate? = nil
calendar.rangeOfUnit(period, startDate: &startDate, interval: nil, forDate: date)
let endDate = calendar.dateByAddingUnit(period, value: 1, toDate: startDate!, options: [])
return (startDate!, endDate!)
}
func calcStartAndEndDateForWeek() {
let (startOfWeek, endOfWeek) = rangeOfPeriod(.WeekOfYear)
print("Start of week = \(dateFormatter.stringFromDate(startOfWeek))")
print("End of the week = \(dateFormatter.stringFromDate(endOfWeek))")
}
func calcStartAndEndDateForDay() {
let (startOfDay, endOfDay) = rangeOfPeriod(.Day)
print("Start of day = \(dateFormatter.stringFromDate(startOfDay))")
print("End of the day = \(dateFormatter.stringFromDate(endOfDay))")
}
init() {
dateFormatter.dateFormat = "dd-MM-yyyy"
}
}
let myDate = Date()
myDate.calcStartAndEndDateForWeek()
myDate.calcStartAndEndDateForDay()
I was implementing something similar and went the following route:
extension Date {
static var startOfToday: Date? {
let date = Date()
guard !date.isStartOfDay else { return date }
return date
.zero(out: .second)?
.zero(out: .minute)?
.zero(out: .hour)?
.addingTimeInterval(-24 * 60 * 60)
}
private func zero(out: Calendar.Component) -> Date? {
return Calendar.current
.date(bySetting: out, value: 0, of: self)
}
private var isStartOfDay: Bool {
let cal = Calendar.current
let hours = cal.component(.hour, from: self)
let minutes = cal.component(.minute, from: self)
let seconds = cal.component(.second, from: self)
return hours == 0 && minutes == 0 && seconds == 0
}
}
Setting a component to zero will increment the next bigger component. So just setting the hour to zero will push the date to the next day at 00:00, unless of course the hour is already at zero. So to make it work for any date we have to zero out the seconds, minutes and hours (in that order). And to make sure we don't end up at the beginning of yesterday we first check if all values aren't already at zero.
I realize this is kinda hacky and probably not the best way to go about this, but it seems to work well enough for my use-case at least.
Getting the end of the day can be built on top of this by just adding another day.