How can you properly support the iOS 12 hour / 24 hour time override in Date & Time settings for DateFormatters?
From the documentation for dateFormat
on DateFormatter
:
You should only set this property when working with fixed format
representations, as discussed in Working With Fixed Format Date
Representations. For user-visible representations, you should use the
dateStyle and timeStyle properties, or the
setLocalizedDateFormatFromTemplate(_:) method if your desired format
cannot be achieved using the predefined styles; both of these
properties and this method provide a localized date representation
appropriate for display to the user.
I still don't really understand why Foundation bothers to modify the date format if you're not using a fixed locale on your date formatter, but it seems the documentation does not recommend using the custom date format for non-fixed format representations.
setLocalizedDateFormatFromTemplate
appears to be what you need if you can't just rely on dateStyle
and timeStyle
, although it doesn't appear to automatically change with a user's 24 hour time setting, so one could check to see if 24 hour time is on before specifying a template…
This method is supposed to allow you to specify a format without losing out on the formatting that is specific to each locale.
EDIT response from Apple Developer Relations (same idea, but more detail):
The correct way to handle this is to avoid specifying "hh" or "HH"
based on locale and instead using the "jj" skeleton symbol in
conjunction with-[NSDateFormatter setLocalizedDateFormatFromTemplate:]
.From the "hour" section of the Date Field Symbol Table
(https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-hour):Input skeleton symbol In such a context, it requests the
preferred hour format for the locale (h, H, K, or K), as determined by
the preferred attribute of the hours element in the supplemental data.By using "jj" you can get a format which best matches the user's
current settings:NSDateFormatter *formatter = [NSDateFormatter new];
[formatter setLocalizedDateFormatFromTemplate:@"jj:mm"];
NSLog(@"%@: 'jj:mm' => '%@' ('%@')", formatter.locale.localeIdentifier, formatter.dateFormat, [formatter stringFromDate:[NSDate date]]);
results in
- en_US (12-hour): "en_US: 'jj:mm' => 'h:mm a' ('1:44 PM')
- en_US (24-hour): "en_US: 'jj:mm' => 'HH:mm' ('13:44')
- en_GB (12-hour): "en_GB: 'jj:mm' => 'h:mm a' ('1:44 pm')
- en_GB (24-hour): "en_GB: 'jj:mm' => 'HH:mm' ('13:44')
This allows you to match your preferred format while keeping with the
user's preferred time settings.
Is there a better way to format Date with DateFormatter according to user's Date & Time preference?
You can use DateFormatter
static method dateFormat(fromTemplate tmplate: String, options opts: Int, locale: Locale?) -> String?
passing j
format and .current
locale and check if it contains "a":
extension DateFormatter {
static var is24Hour: Bool {
dateFormat(fromTemplate: "j", options: 0, locale: .current)?.contains("a") == false
}
}
extension Formatter {
static let customHour: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate("jj")
return dateFormatter
}()
}
extension Date {
var customHour: String { Formatter.customHour.string(from: self) }
}
DateFormatter.is24Hour // false
Date().customHour // "11 AM"
Note that there is no need to check if 24hour setting is on or not unless the user changes it after the formatter has been initialized. If want to make sure it reflects this as well:
extension Formatter {
static let date = DateFormatter()
}
extension Date {
var customHour: String {
Formatter.date.setLocalizedDateFormatFromTemplate("jj")
return Formatter.date.string(from: self)
}
}
Date().customHour // "11 AM"
How to convert date in 12 or 24 hour time format to internet date?
Your question is a little bit confusing. I am assuming that you want to take an input date that either looks like "Jan 15, 2021 22:05" (24-hour time) or "Jan 15, 2021 10:05 PM" (12 hour time) and convert it to standard "internet date" ISO8601DateFormatter date format.
You say you want to determine the input selection based on "device selection". I'm assuming you mean that you expect to see input dates in either 12 or 24 hour format based on the settings on the user's device.
This function will let you figure out the 12/24 hour setting of the user's device:
func dateFormatIs24Hour() -> Bool {
guard let dateFormat = DateFormatter.dateFormat (fromTemplate: "j",
options:0,
locale: Locale.current) else {
return false
}
return !dateFormat.contains("a")
}
You should then be able to generate a date formatter formatString based on the device's 12/24 hour setting:
func MMMddyyyyDateFormat(twentyFourHour: Bool) -> String {
let hourFormat = is24Hour ? "HH" : "hh"
let amOrPmSuffix = is24Hour ? "" : "a"
let dateFormat = "MMM dd, yyyy \(hourFormat):mm \(amOrPmSuffix)"
return dateFormat
}
And you can set up an ISO8601DateFormatter to output dates in "internet date format:
let internetDateFormatter = ISO8601DateFormatter()
internetDateFormatter.formatOptions = .withInternetDateTime
Pulling it all together in a sample viewDidLoad()
function for a shell iOS app:
override func viewDidLoad() {
super.viewDidLoad()
is24Hour = dateFormatIs24Hour()
let inputDateFormatter = DateFormatter()
inputDateFormatter.dateFormat = MMMddyyyyDateFormat(twentyFourHour: is24Hour)
let internetDateFormatter = ISO8601DateFormatter()
internetDateFormatter.formatOptions = .withInternetDateTime
//-----------------------------------------
//This code to generate a value `randomDate` using `thirtyDays` is
//only for testing. It generates a random date that is in a range
//from roughly 30 days in the past to 30 days in the future.
let thirtyDays = 30.0 * 24 * 60 * 60
let randomInterval = Double.random(in: -thirtyDays...thirtyDays)
let randomDate = Date().addingTimeInterval(randomInterval)
//-----------------------------------------
let inputDateString = inputDateFormatter.string(from: randomDate)
var outputDateString = ""
if let convertedDate = inputDateFormatter.date(from: inputDateString) {
outputDateString = internetDateFormatter.string(from: convertedDate)
}
print("Date string \(inputDateString) in Internet format = \(outputDateString)")
}
How to convert 24 hr String time into 12 hr time String format
Use NSDateFormatter
to turn the string in a NSDate
. Then use the dateformatter again to change the NSDate
to a string.
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"HH:mm";
NSDate *date = [dateFormatter dateFromString:@"15:15"];
dateFormatter.dateFormat = @"hh:mm a";
NSString *pmamDateString = [dateFormatter stringFromDate:date];
Code might contain some errors, written without compiling or testing.
Related Topics
Using Compiler Variables in Swift
Split Uint32 into [Uint8] in Swift
How to Cast a Uint64 to an Int64
Modal View Closes When Selecting an Image in Uiwebview iOS
Ui Testing Failure - Neither Element Nor Any Descendant Has Keyboard Focus on Textview
How to Implement iOS 11's Mkusertrackingbutton
Why Does Swift Not Allow Stored Properties in Extensions
Why Does Realitykit Memory Does Not Clear After Deinit Called
Waiting for Alamofire in Unit Tests
Swift Sort Dictionary by Value
Avcapturevideodataoutput Captureoutput Not Being Called
Show Nsmenu Only on Nsstatusbarbutton Right Click
Why Does Swiftui Uihostingcontroller Have Extra Spacing
Monitoring App Switching on Os X