Skip to content

fix(title_case): don't capitalize am/pm in time expressions#3456

Open
jlaportebot wants to merge 8 commits into
Automattic:masterfrom
jlaportebot:fix/title-case-time-am-pm
Open

fix(title_case): don't capitalize am/pm in time expressions#3456
jlaportebot wants to merge 8 commits into
Automattic:masterfrom
jlaportebot:fix/title-case-time-am-pm

Conversation

@jlaportebot
Copy link
Copy Markdown
Contributor

@jlaportebot jlaportebot commented May 21, 2026

Summary

The title case linter incorrectly capitalizes am/pm in time expressions like 9:05am to 9:05Am. Both lowercase and uppercase variants are valid after numbers, so the linter should leave them untouched.

Changes

  • Add is_time_am_pm() helper in title_case.rs that detects when am or pm follows a number (possibly with a colon, e.g. 9:05am or 5pm)
  • Skip title-casing for such words — leave am/pm/AM/PM as-is
  • Add 6 tests for time AM/PM handling in title_case.rs
  • Add 2 linter-level tests in use_title_case.rs

Test Plan

  • cargo check -p harper-core --lib passes
  • Full test suite via CI (cargo test -p harper-core)

Fixes #3454

AI Disclosure

  • I am a human and didn't use any AI.
  • I used LLM features of my editor, but not an agent.
  • I used an AI agent interactively.
  • I am an agent or I got an agent to do the work autonomously.

If Your PR Implements or Enhances a Linter

  • I made up the sentences in the unit tests.
  • The sentences in the unit tests were generated by an AI.
  • I'm using examples from the bug report / feature request.
  • I collected real-world sentences for the unit tests.

Checklist

  • I have performed a self-review of my own code
  • I have added tests to cover my changes
  • I have considered splitting this into smaller pull requests.

The title case linter was incorrectly capitalizing 'am'/'pm' in time
expressions like '9:05am' to '9:05Am'. Both 'am'/'pm' and 'AM'/'PM'
are valid after numbers, so the linter should leave them as-is.

Add is_time_am_pm() helper that detects when 'am' or 'pm' follows a
number (possibly with a colon, e.g. '9:05am' or '5pm'), and skip
title-casing such words.

Fixes Automattic#3454.
Copy link
Copy Markdown
Collaborator

@hippietrail hippietrail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks interesting. Thanks. I noticed one place where you can save a tiny type conversion.

Comment thread harper-core/src/title_case.rs Outdated
let chars = word.get_ch(source);
let chars_lower = chars.to_lower();

if !(chars_lower.as_ref() == ['a', 'm'] || chars_lower.as_ref() == ['p', 'm']) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can avoid .to_lower() and use .eq_ch() from CharStringExt directly. I think there's also .eq_any_ascii_ignore_case() either on the same type or at least on Span too.

@elijah-potter
Copy link
Copy Markdown
Collaborator

@jlaportebot, please update all of your PRs to comply with our agent policy.

…q_any_ignore_ascii_case_str

- Replace .to_lower() + manual comparison with eq_any_ignore_ascii_case_str for cleaner matching (per review feedback)
- Add 'continue' to skip processing when is_time_suffix is true, preventing the else branch from lowercasing AM/PM
@jlaportebot
Copy link
Copy Markdown
Contributor Author

Thanks for the review @hippietrail! I've addressed both points:

  1. Type conversion: Replaced the manual .to_lower() + comparison with eq_any_ignore_ascii_case_str(&["am", "pm"]) — no more unnecessary allocation.
  2. CI failure: The leaves_uppercase_am_after_time test was failing because the is_time_suffix branch only skipped capitalization but didn't continue, so the else branch would still lowercase AM→am. Added continue so time-suffix words are left completely untouched.

The proper-noun dictionary lookup was running before the time-suffix
check, causing 'AM' to be lowercased to 'am' even when it appeared after
a number in a time expression. Moving the is_time_suffix check earlier
ensures time-suffix words are skipped entirely before any case
transformation can occur.
@jlaportebot
Copy link
Copy Markdown
Contributor Author

Updated PR body with the full AI Disclosure section from the template. Also fixed the actual test failure — the is_time_suffix check now runs before the proper-noun dictionary lookup, which was lowercasing AM to am.

@jlaportebot
Copy link
Copy Markdown
Contributor Author

The macOS x64 Desktop check failed, but all other checks (including macOS arm64, Rust checks, JS checks, and all plugin tests) passed. The macOS x64 failure appears to be a CI infrastructure issue — the job logs are no longer available (blob not found), and the title_case change only touches harper-core with no Desktop-specific code. A re-run of the macOS x64 job should resolve this.

…tr variant

Follow reviewer feedback to use char-slice comparisons rather than
string-slice comparisons for the am/pm check, avoiding an implicit
&str → &[char] conversion.
@jlaportebot
Copy link
Copy Markdown
Contributor Author

Thanks @hippietrail — good catch. This has been addressed in the latest commits: the .to_lower() comparison was replaced with eq_any_ignore_ascii_case_chars() on CharString, which avoids the allocation and matches the idiomatic pattern used elsewhere in the codebase. Ready for re-review whenever you have a chance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

“Use title case in headings” triggers on times (AM/PM)

3 participants