@DTS Engineer thank you for the prompt response.
Should I raise a Radar for this? It's not clear to me if this is a bug or if it works as expected.
Is there a workaround or an alternative on how to perform string validation as the user types a string so that the string displayed by the text field updates only on successful validation?
Thus onChange only fires upon successful validation (i.e. change) of the string.
Post
Replies
Boosts
Views
Activity
Can you please provide some clarity on the following?
Is your code allowed to modify the view hierarchy of a cell as part of the prepareForReuse, cellForRowAtIndexPath dance? Or is it unsupported by UIKit, thus will lead to unexpected behaviour?
If it's unsupported, is the proposed solution to create and register a distinct cell for each data that is significantly different to warrant a new cell definition? Is there another way?
I've noticed that the enUSPOSIXDateFormatter does not specify the TimeZone. Looking into the documentation I see that it says "If unspecified, the system time zone is used."
I believe this must be the issue even though I'm pretty sure I remember coming across a different documentation that said "if unspecified, GMT will be used."
I can't recall where I saw that.
I've tried to write a test to further isolate the issue to the formatter.
func testGivenFixedFormatDateAssertComponents() throws {
var calendar: Calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(identifier: "UTC")!
let dateComponents = DateComponents(calendar: calendar,
year: 2023,
month: 3,
day: 8,
hour: 12,
minute: 10)
let given = "08/03/2023 12:10"
let actual = try DateFormatter.date(string: given)
let expected = try XCTUnwrap(dateComponents.date)
XCTAssertEqual(actual, expected) ///XCTAssertEqual failed: ("2023-03-08 17:10:00 +0000") is not equal to ("2023-03-08 12:10:00 +0000")
}
I now had a chance to make the suggested code changes and test this again. Unfortunately, this hasn't solved the issue. Here is the new code:
extension Date {
/// Creates a `user-visible` date string with the given `template` for the given `locale` using the given `dateFormatter`. Effectively, this gives you 3 benefits:
///
/// * You don't need to compute/guess the appropriate date format for every locale
/// * You are guaranteed to always format a date that is localised to the user's locale
/// * You respect user's preference on how to format a date
///
/// The default arguments allow you to to create a `user-visible` date string that is localised to the user's current locale.
///
/// Alternatively, use this method to create the correct date string for the given locale. e.g.
///
/// Given a `template` of `yMMMMd` to create a `user-visible` date string that includes the year, full name of the month and day of the month for a `en_GB` locale, the string returned for a date representing April 1st in 1974 will be in the format of `d MMMM y` (1 April 1974) . While for a `en_US` locale will be in the format of `MMMM d, y` (April 1, 1974)
///
/// - Parameters:
/// - parameter template: A string containing date format patterns (such as “MM” or “h”).
/// - parameter locale: The locale . `Locale.current` by default.
/// - parameter dateFormatter: The `DateFormatter` to generate the date string from this date.
/// - Returns: a `user-visible` string formatted for the given locale. That means that the returned string may not contain exactly those components given in template, but may—for example—have locale-specific adjustments applied.
///
/// - Note: This is the recommended API to use when creating `user-visible` date strings over `.toString(inFormat:)` or `DateConverter.toString(date:format)`
/// - Postcondition: any previously set `dateFormat` for the given `DateFormatter` will be replaced by a date format respecting the given `template` and `locale`.
/// - SeeAlso: http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns
func string(template: String, locale: Locale = Locale.current, using dateFormatter: DateFormatter) -> String {
dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: locale)
return dateFormatter.string(from: self)
}
}
extension DateFormatter {
static let gregorianUTCTimezone: DateFormatter = make(calendar: Calendar(identifier: .gregorian), timeZone: TimeZone(identifier: "UTC")!)
/// Returns a `DateFormatter` that can be used to parse and generate **fixed-format dates** in any date format.
///
/// This date formatter will create dates on the Gregorian Calendar. By default, it does not take into account timezone information.
///
/// This date formatter will behave **consistently** for all users, across devices and simulators with a different locale, calendar and user settings on date & time.
///
/// - Parameters:
/// - parameter calendar: the calendar the date formatter should parse and generate dates in. `.gregorian` by default.
/// - parameter timeZone: the timezone the date formatter should parse and generate dates in. `nil` by default.
/// - SeeAlso: https://developer.apple.com/library/archive/qa/qa1480/_index.html
static func enUSPOSIXDateFormatter(calendar: Calendar = Calendar(identifier: .gregorian), timeZone: TimeZone? = nil) -> DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.calendar = calendar
formatter.timeZone = timeZone
return formatter
}
}
/// Returns a date for the given string using the given `DateFormatter`. Use the default arguments to parse **fixed-format** (string) dates in a reliable way.
///
/// You should **only** use instances returned by `.enUSPOSIXDateFormatter()` to parse and generate dates for any given `dateFormat`.
///
/// Should you wish to provide a different `dateFormat`, e.g. one that includes `TimeZone` information, use `.enUSPOSIXDateFormatter()` to create a
/// `en_US_POSIX` date formatter with a `TimeZone`.
///
/// - Parameters:
/// - parameter string: The date string used with this should be of the fixed.
/// - parameter dateFormat:the format the given `string` must be in. *dd/MM/yyyy HH:mm* by default.
/// - parameter dateFormatter: the `DateFormatter` to parse the given date string. `DateFormatter.enUSPOSIXDateFormatter` by default.
/// - Returns: a date object for the given date `string` as represented by the given `dateFormat`
/// - SeeAlso: `DateFormatter.enUSPOSIXDateFormatter`
static func date(string: String, dateFormat: String = "dd/MM/yyyy HH:mm", dateFormatter: DateFormatter = .enUSPOSIXDateFormatter()) -> Date {
dateFormatter.dateFormat = dateFormat
guard let date = dateFormatter.date(from: string) else {
let error = DateFormatError.UnexpectedFormat(actual: string, expected: dateFormatter.dateFormat)
preconditionFailure(String(reflecting: error))
}
return date
}
Using the above code as shown below, I see the same issue as described in the original post.
func testExample() throws {
let date = DateFormatter.date(string: "08/03/2023 12:10")
// On the computer that failed, this date was at 17:10 (i.e. off by 5 hours)
let actual = date.string(template: "HHmm", locale: Locale(identifier: "en_GB"), using: .gregorianUTCTimezone)
let expected = "12:10"
// One case where actual was 17:10 and expected 12:10.
XCTAssertEqual(actual, expected)
}
I feel like either I have misunderstood something or have made a mistake in the code I am blind to.
Thank you for guiding me through this!
So in other words, assuming I got this right, one way to guarantee an “aligned” array of bytes is by using the same UnsignedInteger, FixedWidthInteger type. Since the array is homogenous, alignment is guaranteed by the size of each type.
e.g. a [UInt8] array that holds byte types
var array: [UInt8] = [0x01, 0x00, 0x03]
var offset = 0
let byte = array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt8.self) }
offset += MemoryLayout<UInt8>.size
let bytes = array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt8.self) }
offset += MemoryLayout<UInt8>.size
let bytess = array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt8.self) }
XCTAssertEqual(byte, 0x01)
XCTAssertEqual(bytes, 0x00)
XCTAssertEqual(bytess, 0x03)
e.g. a [UInt8] array that holds 2 byte types
var array: [UInt8] = [0x01, 0x00, 0x03, 0x0a, 0x00, 0x01]
var offset = 0
let byte = UInt16(bigEndian: array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt16.self) })
offset += MemoryLayout<UInt16>.size
let bytes = UInt16(bigEndian: array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt16.self) })
offset += MemoryLayout<UInt16>.size
let bytess = UInt16(bigEndian: array.withUnsafeBytes { $0.load(fromByteOffset: offset, as: UInt16.self) })
XCTAssertEqual(byte, 0x0100)
XCTAssertEqual(bytes, 0x030a)
XCTAssertEqual(bytess, 0x0001)
The misunderstanding lies in that, it is not possible to have an "aligned" array of bytes when types stored in the array can't be aligned given their mismatched size.
The size of an UInt8 being 1 byte and the size of UInt16 being 2 bytes in this case creating an unaligned array that is not supported by UnsafeRawBufferPointer.load(fromByteOffset:as:).
Please bare with me as I improve my understanding.
What's the use case for UnsafeRawBufferPointer.load(fromByteOffset:as:) when used with an offset?
Can you please provide an example?
Are you suggesting there is no way to satisfy the precondition for UnsafeRawBufferPointer.load(fromByteOffset:as:), when using an offset, in Swift?