| 1382 |
1382 |
|
let ctx = Arc::new(Ctx::new(pool));
|
| 1383 |
1383 |
|
let reg = tools::registry(ctx.clone());
|
| 1384 |
1384 |
|
|
| 1385 |
|
- |
// 06:00 as a routine, not as an instant.
|
|
1385 |
+ |
// 06:00 as a routine, not as an instant. A relative event's civil time is
|
|
1386 |
+ |
// rendered in whatever zone the machine reports, so the instant is built
|
|
1387 |
+ |
// from that zone rather than written as a fixed -06:00 offset: hard-coding
|
|
1388 |
+ |
// one made the assertion below pass only on Mountain-time hosts.
|
|
1389 |
+ |
use chrono::TimeZone as _;
|
|
1390 |
+ |
let home = goingson_core::tz::system_tz();
|
|
1391 |
+ |
let start_at = home
|
|
1392 |
+ |
.with_ymd_and_hms(2026, 8, 3, 6, 0, 0)
|
|
1393 |
+ |
.earliest()
|
|
1394 |
+ |
.expect("06:00 on this date exists in every zone");
|
|
1395 |
+ |
let end_at = home
|
|
1396 |
+ |
.with_ymd_and_hms(2026, 8, 3, 6, 30, 0)
|
|
1397 |
+ |
.earliest()
|
|
1398 |
+ |
.expect("06:30 on this date exists in every zone");
|
|
1399 |
+ |
|
| 1386 |
1400 |
|
let created = call(
|
| 1387 |
1401 |
|
®,
|
| 1388 |
1402 |
|
"create_event",
|
| 1389 |
1403 |
|
json!({
|
| 1390 |
1404 |
|
"title": "Wake, feed cats",
|
| 1391 |
|
- |
"start": "2026-08-03T06:00:00-06:00",
|
| 1392 |
|
- |
"end": "2026-08-03T06:30:00-06:00",
|
|
1405 |
+ |
"start": start_at.to_rfc3339(),
|
|
1406 |
+ |
"end": end_at.to_rfc3339(),
|
| 1393 |
1407 |
|
"tz_kind": "relative",
|
| 1394 |
1408 |
|
"recurrence": { "pattern": "Weekly", "interval": 1, "weekdays": [0, 1, 2, 3, 4] },
|
| 1395 |
1409 |
|
}),
|
| 1401 |
1415 |
|
assert_eq!(got["tz_kind"], "relative");
|
| 1402 |
1416 |
|
assert_eq!(got["start_local"], "2026-08-03 06:00:00");
|
| 1403 |
1417 |
|
|
| 1404 |
|
- |
// Now the machine moves to Lisbon. The civil time is the truth, so the
|
| 1405 |
|
- |
// recompute must move the UTC instant to keep the wall clock at 06:00.
|
| 1406 |
|
- |
let lisbon: chrono_tz::Tz = "Europe/Lisbon".parse().unwrap();
|
| 1407 |
|
- |
let moved = goingson_core::tz::rematerialize_civil_events(&ctx.events(), ctx.user_id, lisbon)
|
|
1418 |
+ |
// Now the machine moves somewhere the wall clock genuinely differs. Lisbon
|
|
1419 |
+ |
// is the usual destination, but it shares its August offset with a band of
|
|
1420 |
+ |
// European hosts, and on one of those nothing would move for the wrong
|
|
1421 |
+ |
// reason; Tokyo covers that case.
|
|
1422 |
+ |
let away = ["Europe/Lisbon", "Asia/Tokyo"]
|
|
1423 |
+ |
.into_iter()
|
|
1424 |
+ |
.map(|name| name.parse::<chrono_tz::Tz>().unwrap())
|
|
1425 |
+ |
.find(|zone| {
|
|
1426 |
+ |
start_at.with_timezone(zone).naive_local()
|
|
1427 |
+ |
!= start_at.with_timezone(&home).naive_local()
|
|
1428 |
+ |
})
|
|
1429 |
+ |
.expect("one of the two reads differently from the host zone");
|
|
1430 |
+ |
|
|
1431 |
+ |
// The civil time is the truth, so the recompute must move the UTC instant
|
|
1432 |
+ |
// to keep the wall clock at 06:00.
|
|
1433 |
+ |
let moved = goingson_core::tz::rematerialize_civil_events(&ctx.events(), ctx.user_id, away)
|
| 1408 |
1434 |
|
.expect("recompute");
|
| 1409 |
1435 |
|
assert_eq!(moved, 1, "the relative event should have been remapped");
|
| 1410 |
1436 |
|
|
| 1411 |
1437 |
|
let after = call(®, "get_event", json!({ "id": id })).await;
|
| 1412 |
1438 |
|
// Civil truth untouched...
|
| 1413 |
1439 |
|
assert_eq!(after["start_local"], "2026-08-03 06:00:00");
|
| 1414 |
|
- |
// ...and the instant now reads 06:00 in Lisbon rather than in Denver.
|
|
1440 |
+ |
// ...and the instant now reads 06:00 there rather than at home.
|
| 1415 |
1441 |
|
let start: chrono::DateTime<chrono::Utc> = after["start"].as_str().unwrap().parse().unwrap();
|
| 1416 |
1442 |
|
assert_eq!(
|
| 1417 |
|
- |
start.with_timezone(&lisbon).format("%H:%M").to_string(),
|
|
1443 |
+ |
start.with_timezone(&away).format("%H:%M").to_string(),
|
| 1418 |
1444 |
|
"06:00"
|
| 1419 |
1445 |
|
);
|
| 1420 |
1446 |
|
}
|