Posts

Post marked as solved
4 Replies
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.
Post marked as solved
4 Replies
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") }
Post marked as solved
4 Replies
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.
Post not yet marked as solved
6 Replies
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:).
Post not yet marked as solved
6 Replies
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?