Add locale-based time formatting in the chat panel (#7945)

evrsen created

This commit addresses the issue of time formats displayed in the chat
panel. Previously, the time was always displayed in a 12-hour format,
regardless of the user's locale. The `format_timestamp` function has
been updated to check the system locale and format the time accordingly.


Release Notes:
- Fixed time formatting in the chat panel to be locale based.

Change summary

Cargo.lock                         |   1 
Cargo.toml                         |   1 
crates/collab_ui/Cargo.toml        |   1 
crates/collab_ui/src/chat_panel.rs | 138 ++++++++++++++++++++++++++-----
4 files changed, 118 insertions(+), 23 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2115,6 +2115,7 @@ dependencies = [
  "settings",
  "smallvec",
  "story",
+ "sys-locale",
  "theme",
  "theme_selector",
  "time",

Cargo.toml 🔗

@@ -272,6 +272,7 @@ unindent = "0.1.7"
 url = "2.2"
 uuid = { version = "1.1.2", features = ["v4"] }
 wasmtime = "16"
+sys-locale = "0.3.1"
 
 [patch.crates-io]
 tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "1d8975319c2d5de1bf710e7e21db25b0eee4bc66" }

crates/collab_ui/Cargo.toml 🔗

@@ -66,6 +66,7 @@ util.workspace = true
 vcs_menu.workspace = true
 workspace.workspace = true
 zed_actions.workspace = true
+sys-locale.workspace = true
 
 [dev-dependencies]
 call = { workspace = true, features = ["test-support"] }

crates/collab_ui/src/chat_panel.rs 🔗

@@ -480,6 +480,7 @@ impl ChatPanel {
                                     OffsetDateTime::now_utc(),
                                     message.timestamp,
                                     self.local_timezone,
+                                    None,
                                 ))
                                 .size(LabelSize::Small)
                                 .color(Color::Muted),
@@ -917,26 +918,58 @@ impl Panel for ChatPanel {
 
 impl EventEmitter<PanelEvent> for ChatPanel {}
 
+fn is_12_hour_clock(locale: String) -> bool {
+    [
+        "es-MX", "es-CO", "es-SV", "es-NI",
+        "es-HN", // Mexico, Colombia, El Salvador, Nicaragua, Honduras
+        "en-US", "en-CA", "en-AU", "en-NZ", // U.S, Canada, Australia, New Zealand
+        "ar-SA", "ar-EG", "ar-JO", // Saudi Arabia, Egypt, Jordan
+        "en-IN", "hi-IN", // India, Hindu
+        "en-PK", "ur-PK", // Pakistan, Urdu
+        "en-PH", "fil-PH", // Philippines, Filipino
+        "bn-BD", "ccp-BD", // Bangladesh, Chakma
+        "en-IE", "ga-IE", // Ireland, Irish
+        "en-MY", "ms-MY", // Malaysia, Malay
+    ]
+    .contains(&locale.as_str())
+}
+
 fn format_timestamp(
     reference: OffsetDateTime,
     timestamp: OffsetDateTime,
     timezone: UtcOffset,
+    locale: Option<String>,
 ) -> String {
+    let locale = match locale {
+        Some(locale) => locale,
+        None => sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")),
+    };
     let timestamp_local = timestamp.to_offset(timezone);
     let timestamp_local_hour = timestamp_local.hour();
+    let timestamp_local_minute = timestamp_local.minute();
 
-    let hour_12 = match timestamp_local_hour {
-        0 => 12,                              // Midnight
-        13..=23 => timestamp_local_hour - 12, // PM hours
-        _ => timestamp_local_hour,            // AM hours
-    };
-    let meridiem = if timestamp_local_hour >= 12 {
-        "pm"
+    let (hour, meridiem) = if is_12_hour_clock(locale) {
+        let meridiem = if timestamp_local_hour >= 12 {
+            "pm"
+        } else {
+            "am"
+        };
+
+        let hour_12 = match timestamp_local_hour {
+            0 => 12,                              // Midnight
+            13..=23 => timestamp_local_hour - 12, // PM hours
+            _ => timestamp_local_hour,            // AM hours
+        };
+
+        (hour_12, Some(meridiem))
     } else {
-        "am"
+        (timestamp_local_hour, None)
+    };
+
+    let formatted_time = match meridiem {
+        Some(meridiem) => format!("{:02}:{:02} {}", hour, timestamp_local_minute, meridiem),
+        None => format!("{:02}:{:02}", hour, timestamp_local_minute),
     };
-    let timestamp_local_minute = timestamp_local.minute();
-    let formatted_time = format!("{:02}:{:02} {}", hour_12, timestamp_local_minute, meridiem);
 
     let reference_local = reference.to_offset(timezone);
     let reference_local_date = reference_local.date();
@@ -950,12 +983,20 @@ fn format_timestamp(
         return format!("yesterday at {}", formatted_time);
     }
 
-    format!(
-        "{:02}/{:02}/{}",
-        timestamp_local_date.month() as u32,
-        timestamp_local_date.day(),
-        timestamp_local_date.year()
-    )
+    match meridiem {
+        Some(_) => format!(
+            "{:02}/{:02}/{}",
+            timestamp_local_date.month() as u32,
+            timestamp_local_date.day(),
+            timestamp_local_date.year()
+        ),
+        None => format!(
+            "{:02}/{:02}/{}",
+            timestamp_local_date.day(),
+            timestamp_local_date.month() as u32,
+            timestamp_local_date.year()
+        ),
+    }
 }
 
 #[cfg(test)]
@@ -1015,13 +1056,34 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_format_locale() {
+        let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
+        let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
+
+        assert_eq!(
+            format_timestamp(
+                reference,
+                timestamp,
+                test_timezone(),
+                Some(String::from("en-GB"))
+            ),
+            "15:30"
+        );
+    }
+
     #[test]
     fn test_format_today() {
         let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
         let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
 
         assert_eq!(
-            format_timestamp(reference, timestamp, test_timezone()),
+            format_timestamp(
+                reference,
+                timestamp,
+                test_timezone(),
+                Some(String::from("en-US"))
+            ),
             "03:30 pm"
         );
     }
@@ -1032,7 +1094,12 @@ mod tests {
         let timestamp = create_offset_datetime(1990, 4, 11, 9, 0, 0);
 
         assert_eq!(
-            format_timestamp(reference, timestamp, test_timezone()),
+            format_timestamp(
+                reference,
+                timestamp,
+                test_timezone(),
+                Some(String::from("en-US"))
+            ),
             "yesterday at 09:00 am"
         );
     }
@@ -1043,7 +1110,12 @@ mod tests {
         let timestamp = create_offset_datetime(1990, 4, 11, 20, 0, 0);
 
         assert_eq!(
-            format_timestamp(reference, timestamp, test_timezone()),
+            format_timestamp(
+                reference,
+                timestamp,
+                test_timezone(),
+                Some(String::from("en-US"))
+            ),
             "yesterday at 08:00 pm"
         );
     }
@@ -1054,7 +1126,12 @@ mod tests {
         let timestamp = create_offset_datetime(1990, 4, 11, 18, 0, 0);
 
         assert_eq!(
-            format_timestamp(reference, timestamp, test_timezone()),
+            format_timestamp(
+                reference,
+                timestamp,
+                test_timezone(),
+                Some(String::from("en-US"))
+            ),
             "yesterday at 06:00 pm"
         );
     }
@@ -1065,7 +1142,12 @@ mod tests {
         let timestamp = create_offset_datetime(1990, 4, 11, 23, 55, 0);
 
         assert_eq!(
-            format_timestamp(reference, timestamp, test_timezone()),
+            format_timestamp(
+                reference,
+                timestamp,
+                test_timezone(),
+                Some(String::from("en-US"))
+            ),
             "yesterday at 11:55 pm"
         );
     }
@@ -1076,7 +1158,12 @@ mod tests {
         let timestamp = create_offset_datetime(1990, 4, 1, 20, 0, 0);
 
         assert_eq!(
-            format_timestamp(reference, timestamp, test_timezone()),
+            format_timestamp(
+                reference,
+                timestamp,
+                test_timezone(),
+                Some(String::from("en-US"))
+            ),
             "yesterday at 08:00 pm"
         );
     }
@@ -1087,7 +1174,12 @@ mod tests {
         let timestamp = create_offset_datetime(1990, 4, 10, 20, 20, 0);
 
         assert_eq!(
-            format_timestamp(reference, timestamp, test_timezone()),
+            format_timestamp(
+                reference,
+                timestamp,
+                test_timezone(),
+                Some(String::from("en-US"))
+            ),
             "04/10/1990"
         );
     }