Rust 時區處理

當在 web 應用程序中處理日期和時間時,時區的處理可能會成爲一個問題。尤其是爲世界各地用戶服務的應用程序。

在本篇文章中,將使用 Chrono 和 Chrono- tz 庫處理 Rust 中的日期和時區。Chrono 是一個在 Rust 中處理日期和時間的庫,Chrono- tz 是一個處理時區的擴展。

我們將構建一個簡單的 web 服務,允許用戶以 RFC3339 格式 (“1996-12-19T16:39:57+02:00”) 添加日期,其中包括時區。然後,這些日期被保存在內存中,用戶可以獲取它們並將它們轉換爲自己選擇的時區。

在構建這個應用程序時,將演示如何在 Rust 中解析和格式化日期,以及如何解析和轉換時區。

構建項目

cargo new rust-timezone-example

接下來,編輯 Cargo.toml 文件,並添加必要的依賴項:

[dependencies]
tokio = { version = "0.2", features = ["macros", "rt-threaded", "sync"] }
warp = "0.2"
chrono = { version = "0.4", features = ["serde"] }
chrono-tz = "0.5"
serde = {version = "1.0", features = ["derive"] }
serde_json = "1.0"

數據存儲

我們使用內存進行數據存儲,共享一個存放日期的 vector。

use chrono::prelude::*;
use tokio::sync::RwLock;
use std::sync::Arc;
type Dates = Arc<RwLock<Vec<DateTime<Utc>>>>;

Dates 定義了日期的數據存儲,使用 UTC 作爲默認時區。這樣,我們就不必跟蹤傳入日期的時區。DateTime 和 Utc 類型來自 Chrono 庫。

我們想保存一個日期列表,所以這些日期被放入 Vec 中。因爲可能會在同一時間從不同的線程讀取和寫入這個數據存儲,我們使用了一個來自 tokio 運行時的異步 RwLock,這樣任何想要訪問它的人都必須獲得一個鎖。RwLock 可以允許多個線程讀,但是隻能有一個寫線程是活躍的,這保證我們不會有任何數據競爭。

最後,我們使用了 Arc,這樣我們就可以以一種內存安全的方式在線程之間共享它。

Web server

下一步是設置一個基本的 web 服務,並將其與這個臨時數據存儲連接起來。讓我們從 main.rs 開始:

use chrono::prelude::*;
use tokio::sync::RwLock;
use std::sync::Arc;
use warp::{http::StatusCode, reply, Filter, Rejection, Reply};
use std::convert::Infallible;
type Dates = Arc<RwLock<Vec<DateTime<Utc>>>>;
#[derive(Deserialize)]
struct DateTimeRequest {
    date_time: String,
}
#[tokio::main]
async fn main() {
    let dates: Dates = Arc::new(RwLock::new(Vec::new()));
    let create_route = warp::path("create")
        .and(warp::post())
        .and(with_dates(dates.clone()))
        .and(warp::body::json())
        .and_then(create_handler);
    let fetch_route = warp::path("fetch")
        .and(warp::get())
        .and(warp::path::param())
        .and(with_dates(dates.clone()))
        .and_then(fetch_handler);
    println!("Server started at localhost:8080");
    warp::serve(create_route.or(fetch_route))
        .run(([0, 0, 0, 0], 8080))
        .await;
}
fn with_dates(dates: Dates) -> impl Filter<Extract = (Dates,), Error = Infallible> + Clone {
    warp::any().map(move || dates.clone())
}

我們創建了兩個路由,一個用於 POST /create,另一個用於 GET /fetch/$timezone。在這兩種情況下,我們都通過名爲 with_dates 的 warp 過濾器將日期數據存儲 (在 main 開頭初始化) 傳遞給 handler 程序,該過濾器只是將原子引用複製到處理程序中。

最後,我們需要爲這兩個路由定義 handler 函數:

type Result<T> = std::result::Result<T, Rejection>;
async fn create_handler(dates: Dates, body: DateTimeRequest) -> Result<impl Reply> {
    Ok("")
}
async fn fetch_handler(time_zone: String, dates: Dates) -> Result<impl Reply> {
    Ok("")
}

創建日期

第一步是解析傳入的 body.date_time,將 Date_time 字符串轉換爲有效日期。如果成功,我們將日期轉換爲 UTC 並將其推送到數據存儲,返回一條成功消息。

async fn create_handler(dates: Dates, body: DateTimeRequest) -> Result<impl Reply> {
    let dt: DateTime<FixedOffset> = match DateTime::parse_from_rfc3339(&body.date_time) {
        Ok(v) => v,
        Err(e) => {
            return Ok(reply::with_status(
                format!("could not parse date: {}", e),
                StatusCode::BAD_REQUEST,
            ))
        }
    };
    dates.write().await.push(dt.with_timezone(&Utc));
    Ok(reply::with_status(
        format!("Added date with timezone: {} as UTC", dt.timezone()),
        StatusCode::OK,
    ))
}

我們使用 Chrono 的 DateTime::parse_from_rfc3339 函數來解析日期,這裏使用的是 DateTime,用偏移量用來描述時區。在 Chrono 中,還有更多方法可以解析用戶任意定義的日期格式。

如果失敗,則返回一個錯誤。否則,將使用 date .write().await 獲得對數據存儲的寫鎖,轉換爲 UTC,存儲到日期 vector 中。

最後,我們向用戶返回一條成功消息,向他們顯示從傳入的日期中解析出的時區。

獲取日期

要獲取所有保存的日期,用戶可以使用 GET /fetch/$timezone 路由,其中 $timezone 可能是 UTC、GMT、Africa/Algiers 或其他時區。

下一步是解析傳入的時區,如果它是有效的時區,則轉換爲該時區下的所有保存的日期返回給用戶。

async fn fetch_handler(time_zone: String, dates: Dates) -> Result<impl Reply> {
    let parsed_time_zone = time_zone.replace("%2F", "/");
    let tz: Tz = match parsed_time_zone.parse() {
        Ok(v) => v,
        Err(e) => {
            return Ok(reply::with_status(
                format!("could not parse timezone: {}", e),
                StatusCode::BAD_REQUEST,
            ))
        }
    };
    Ok(
        match serde_json::to_string(
            &dates
                .read()
                .await
                .iter()
                .map(|t: &DateTime<Utc>| t.with_timezone(&tz).to_rfc3339())
                .collect::<Vec<_>>(),
        ) {
            Ok(v) => reply::with_status(v, StatusCode::OK),
            Err(e) => {
                return Ok(reply::with_status(
                    format!("could not serialize json: {}", e),
                    StatusCode::INTERNAL_SERVER_ERROR,
                ))
            }
        },
    )
}

我們使用. parse() 函數將傳入字符串解析爲 chrono_tz::Tz。如果失敗,則返回一個錯誤。

下一步是使用 date .read().await 獲取數據存儲上的讀鎖,然後迭代該向量,使用. with_timezone() 函數將 UTC 日期轉換到給定時區的日期。

最後,我們將日期轉換爲 RFC3339 日期字符串,以與輸入一致。然後將得到的向量序列化爲 JSON 並返回給用戶。

就這樣!讓我們看看它是否像我們期望的那樣工作。

首先,使用 cargo run 運行程序,然後使用 cURL 創建日期:

curl -X POST http://localhost:8080/create -d '{"date_time": "1996-12-19T16:39:57+02:00"}' -H "content-type: application/json"
Added date with timezone: +02:00 as UTC

到目前爲止,一切順利。我們先在 UTC 中獲取它:

curl http://localhost:8080/fetch/UTC
["1996-12-19T14:39:57+00:00"]

然後測試另一個時區:非洲 / 阿爾及爾

curl http://localhost:8080/fetch/Africa%2FAlgiers
["1996-12-19T15:39:57+01:00"]

你可以在不同的時區組合中進行測試。

本文翻譯自:

https://blog.logrocket.com/timezone-handling-in-rust-with-chrono-tz/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/_zE_96SuPp-UepdR9_4s-w