Use macOS API to retrieve the local timezone

Antonio Scandurra and Max Brunsfeld created

The `time` crate currently doesn't have a reliable way to get that.
In the future, `NSSystemTimeZoneDidChangeNotification` could be
used to keep the cached timezone up-to-date.

Co-Authored-By: Max Brunsfeld <max@zed.dev>

Change summary

Cargo.lock                        |  1 +
gpui/Cargo.toml                   |  1 +
gpui/src/platform.rs              |  3 +++
gpui/src/platform/mac/platform.rs |  9 +++++++++
gpui/src/platform/test.rs         |  5 +++++
zed/Cargo.toml                    |  2 +-
zed/src/chat_panel.rs             | 15 ++++++++++-----
7 files changed, 30 insertions(+), 6 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2187,6 +2187,7 @@ dependencies = [
  "simplelog",
  "smallvec",
  "smol",
+ "time 0.3.2",
  "tiny-skia",
  "tree-sitter",
  "usvg",

gpui/Cargo.toml 🔗

@@ -27,6 +27,7 @@ serde = { version = "1.0.125", features = ["derive"] }
 serde_json = "1.0.64"
 smallvec = { version = "1.6", features = ["union"] }
 smol = "1.2"
+time = { version = "0.3" }
 tiny-skia = "0.5"
 tree-sitter = "0.19"
 usvg = "0.14"

gpui/src/platform.rs 🔗

@@ -26,6 +26,7 @@ use std::{
     rc::Rc,
     sync::Arc,
 };
+use time::UtcOffset;
 
 pub trait Platform: Send + Sync {
     fn dispatcher(&self) -> Arc<dyn Dispatcher>;
@@ -49,6 +50,8 @@ pub trait Platform: Send + Sync {
     fn read_credentials(&self, url: &str) -> Option<(String, Vec<u8>)>;
 
     fn set_cursor_style(&self, style: CursorStyle);
+
+    fn local_timezone(&self) -> UtcOffset;
 }
 
 pub(crate) trait ForegroundPlatform {

gpui/src/platform/mac/platform.rs 🔗

@@ -42,6 +42,7 @@ use std::{
     slice, str,
     sync::Arc,
 };
+use time::UtcOffset;
 
 const MAC_PLATFORM_IVAR: &'static str = "platform";
 static mut APP_CLASS: *const Class = ptr::null();
@@ -558,6 +559,14 @@ impl platform::Platform for MacPlatform {
             let _: () = msg_send![cursor, set];
         }
     }
+
+    fn local_timezone(&self) -> UtcOffset {
+        unsafe {
+            let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
+            let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
+            UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
+        }
+    }
 }
 
 unsafe fn get_foreground_platform(object: &mut Object) -> &MacForegroundPlatform {

gpui/src/platform/test.rs 🔗

@@ -9,6 +9,7 @@ use std::{
     rc::Rc,
     sync::Arc,
 };
+use time::UtcOffset;
 
 pub struct Platform {
     dispatcher: Arc<dyn super::Dispatcher>,
@@ -136,6 +137,10 @@ impl super::Platform for Platform {
     fn set_cursor_style(&self, style: CursorStyle) {
         *self.cursor.lock() = style;
     }
+
+    fn local_timezone(&self) -> UtcOffset {
+        UtcOffset::UTC
+    }
 }
 
 impl Window {

zed/Cargo.toml 🔗

@@ -49,7 +49,7 @@ smallvec = { version = "1.6", features = ["union"] }
 smol = "1.2.5"
 surf = "2.2"
 tempdir = { version = "0.3.7", optional = true }
-time = { version = "0.3", features = ["local-offset"] }
+time = { version = "0.3" }
 tiny_http = "0.8"
 toml = "0.5"
 tree-sitter = "0.19.5"

zed/src/chat_panel.rs 🔗

@@ -25,6 +25,7 @@ pub struct ChatPanel {
     input_editor: ViewHandle<Editor>,
     channel_select: ViewHandle<Select>,
     settings: watch::Receiver<Settings>,
+    local_timezone: UtcOffset,
 }
 
 pub enum Event {}
@@ -94,6 +95,7 @@ impl ChatPanel {
             input_editor,
             channel_select,
             settings,
+            local_timezone: cx.platform().local_timezone(),
         };
 
         this.init_active_channel(cx);
@@ -204,7 +206,7 @@ impl ChatPanel {
                         .with_child(
                             Container::new(
                                 Label::new(
-                                    format_timestamp(message.timestamp, now),
+                                    format_timestamp(message.timestamp, now, self.local_timezone),
                                     theme.timestamp.text.clone(),
                                 )
                                 .boxed(),
@@ -314,10 +316,13 @@ impl View for ChatPanel {
     }
 }
 
-fn format_timestamp(mut timestamp: OffsetDateTime, mut now: OffsetDateTime) -> String {
-    let local_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
-    timestamp = timestamp.to_offset(local_offset);
-    now = now.to_offset(local_offset);
+fn format_timestamp(
+    mut timestamp: OffsetDateTime,
+    mut now: OffsetDateTime,
+    local_timezone: UtcOffset,
+) -> String {
+    timestamp = timestamp.to_offset(local_timezone);
+    now = now.to_offset(local_timezone);
 
     let today = now.date();
     let date = timestamp.date();