Parses the human-readable timestamp Cursor injects into user prompts as ` … ` (e.g. `Wednesday, Jun 10, 2026, 9:11 AM (UTC+2)`) into Unix epoch seconds. Cursor transcript JSONL carries no structured per-message timestamps, so this tag is the only per-message time signal available to ingest. The parser is tolerant: the weekday is optional, the clock accepts 12-hour (`AM`/`PM`)
(value: &str)
| 179 | /// (`AM`/`PM`) or 24-hour form, and the offset accepts `(UTC)`, `(UTC±H)`, |
| 180 | /// and `(UTC±H:MM)`. |
| 181 | pub fn parse_cursor_human_timestamp(value: &str) -> Option<i64> { |
| 182 | let parts: Vec<&str> = value.split(',').map(str::trim).collect(); |
| 183 | // [weekday,] "Jun 10", "2026", "9:11 AM (UTC+2)" |
| 184 | let (month_day, year_part, time_part) = match parts.as_slice() { |
| 185 | [_, month_day, year, time] | [month_day, year, time] => (*month_day, *year, *time), |
| 186 | _ => return None, |
| 187 | }; |
| 188 | |
| 189 | let mut md = month_day.split_whitespace(); |
| 190 | let month = month_number(md.next()?)?; |
| 191 | let day: u32 = md.next()?.parse().ok()?; |
| 192 | if md.next().is_some() { |
| 193 | return None; |
| 194 | } |
| 195 | let year: i32 = year_part.parse().ok()?; |
| 196 | if day == 0 || day > days_in_month(year, month) { |
| 197 | return None; |
| 198 | } |
| 199 | |
| 200 | let mut clock = time_part.split_whitespace(); |
| 201 | let hour_minute = clock.next()?; |
| 202 | let (hour_text, minute_text) = hour_minute.split_once(':')?; |
| 203 | let mut hour: u32 = hour_text.parse().ok()?; |
| 204 | let minute: u32 = minute_text.parse().ok()?; |
| 205 | let mut rest = clock.next(); |
| 206 | match rest.map(str::to_ascii_uppercase).as_deref() { |
| 207 | Some("AM") => { |
| 208 | if !(1..=12).contains(&hour) { |
| 209 | return None; |
| 210 | } |
| 211 | hour %= 12; |
| 212 | rest = clock.next(); |
| 213 | } |
| 214 | Some("PM") => { |
| 215 | if !(1..=12).contains(&hour) { |
| 216 | return None; |
| 217 | } |
| 218 | hour = hour % 12 + 12; |
| 219 | rest = clock.next(); |
| 220 | } |
| 221 | _ => {} |
| 222 | } |
| 223 | if hour > 23 || minute > 59 { |
| 224 | return None; |
| 225 | } |
| 226 | let offset_seconds = match rest { |
| 227 | Some(zone) => parse_utc_offset(zone)?, |
| 228 | None => 0, |
| 229 | }; |
| 230 | if clock.next().is_some() { |
| 231 | return None; |
| 232 | } |
| 233 | |
| 234 | let days = days_from_civil(year, month, day); |
| 235 | let local_seconds = days * 86_400 + i64::from(hour) * 3_600 + i64::from(minute) * 60; |
| 236 | let timestamp = local_seconds - offset_seconds; |
| 237 | (timestamp >= 0).then_some(timestamp) |
| 238 | } |
no test coverage detected