当在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<FixedOffset>,用偏移量用来描述时区。在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/