Working with AttributedString: how to the the string value of the 2nd run?

I need the locale specific time components separator as in "15:34:23" the ":" for the US locale. I can find no way to get this out of Locale. So I use Foundation's Date.FormatStyle to format a time string and I can get the separator string out of it. I use AttributedString. Is there anyway to directly address at the separator part of the AttributedString?

Xcode 15 auto-complete totally doesn’t work for me. I used to be able to type run. and get a lot to see. But now nothing.

    // get the locale specific time components separator
    static let (timeSeparator, clockDisplayTemplate) = {
        // create a Date with time at 11:11:11
        let calendar = Calendar.current
        var dateComponents = DateComponents()
        dateComponents.hour = 11 // Hour (24-hour format)
        dateComponents.minute = 11
        dateComponents.second = 11
        

        // Create a Date with the specified components
        guard let myDate = calendar.date(bySettingHour: dateComponents.hour!,
                                         minute: dateComponents.minute!,
                                         second: dateComponents.second!,
                                         of: Date()) else {
            fatalError("Created an invalid Date for creating clock template")
        }
        
        
        let timeAttributedString = myDate.formatted(
            .dateTime.hour(.twoDigits(amPM: .omitted))
            .minute(.twoDigits)
            .attributed
        )

        // get at the third character
        let timeSeparator = timeAttributedString.characters[
            timeAttributedString.characters.index(timeAttributedString.characters.startIndex, offsetBy: 2)
        ]
        
        return ("\(timeSeparator)", "88\(timeSeparator)88\(timeSeparator)88")
    }()

Can you give a higher level overview on what exactly you want to achieve?

Have a look at this one:

locale -ck LC_TIME
...
t_fmt_ampm="%I:%M:%S %p"
t_fmt="%H:%M:%S"

Note that there is no variable specifically for "time component separator": the four colons in the above two strings could (in theory) be different symbols:

10:20-30
10h20'30 PM

I wonder why.. looking at your code you don't seem to be using the attributeness of that string in any way.

cc-ing @eskimo who's likely to know the answer.

I need to display the time in 24-hour format regardless of locale (using this method). I need to know what's the separators for time display in hour minute second.

I wish I know how. Usually someFormatStyle.attributed usually produce an AttributedString with all the different parts annotated such that each run indicate what it is, something like this:

for run in result.runs where run.inlinePresentationIntent == .code {
  result[run.range].foregroundColor = .red
  result[run.range].backgroundColor = .mint
}
1 Like

I remember that thread, lots of material in there. The conclusion was that the new API is not capable enough compared to the old API. It's still the case today.

This is not a well-defined question. Older versions of macOS allow you completely customize the time format, including the ability to specify arbitrary separators for different components:

1 Like

This is why I don't want to hardcode to ":". This is something Foundation should deal with, which it does with Date.FormatStyle. The problem for me is I want to display always in 24-hour format and the only way I found so far is with Date.VerbatimFormatStyle, but this doesn't do localization so I need to supply the appropriate time components separator(s).

I’m saying “the time separator” is an invalid concept. On certain OSes, the user can individually control the separator between the hour and minute segments as well as the minute and second segments, or delete them entirely:

This is the same point that @tera made above.

Rather than trying to copy placeholders into your format string, I think you want to get a format string and replace its 12-hour format token with a 24-hour token.

let df = DateFormatter() 
df.dateStyle = .none 
df.timeStyle = .medium 
print(df.string(from: .now)) // 2:35:27 PM

let newFormat = NSMutableString(string: df.dateFormat!) 
newFormat.replaceOccurrences(of: "h", with: "H", range: NSMakeRange(0, newFormat.length)) 
newFormat.replaceOccurrences(of: "k", with: "K", range: NSMakeRange(0, newFormat.length)) 
df.dateFormat = String(newFormat) 
print(df.string(from: .now)) // 14:35:27 PM
2 Likes

I am not only displaying time in hours minutes seconds in 24 format. Additionally, I need this string with the correct “time separators”. This second part is why I need the separators.

Again, there is no such thing as a time separator. There is a string that contains tokens. Everything surrounding that token is used verbatim. Someone could change their time format to "hh(HH) mm.ss", which would print as 02(14) 35.27. What’s “the separator” there?

The approach I provided above generalizes to any format. You first generate a localized format by setting the .dateStyle and .timeStyle and reading the .dateFormat, substitute any hs with Hs and ks with Ks, and re-assign to the dateFormat property.

1 Like

Is this not working for you?

let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("Hmm")
print(formatter.dateFormat) // HH:mm
let result = formatter.string(from: date) // 23:14 on US locale

It was suggested by @eskimo in the mentioned thread.

3 Likes

For what I need, the old Formatter is fine.