Converting a UTC time via Timezones - rust

I am displaying a date on the screen using the chrono crate.
The intention is to show the date in the users preferred time or UTC if no is set.
I have the UTC default set up, but I am unsure on the best method to record the user's timezone and how to apply that to the current date.
Note: date might not be set here so I'd prefer to modify date rather than use a different constructor.
let mut date: DateTime<UTC> = UTC::now();
//Convert to the User's Timezone if present
if let Some(user) = user {
//Extract the timezone
date.with_timezone(TimeZone::from_offset(&user.timezone));
}
let date_text = date.format("%H:%M %d/%m/%y").to_string();
What I would like is a type to use for user.timezone and an example on how to set the date.

You can use the chrono-tz crate, which allows you to convert a string to a timezone with chrono_tz::Tz::from_str("Europe/Berlin"). So all your user has to do is to supply a valid timezone string.
You can then use
fn date_time_str<Tz: chrono::TimeZone>(date: DateTime<Tz>, user: User) -> String {
if let Some(user) = user {
if let Ok(tz) = chrono_tz::Tz::from_str(user.timezone) {
let newdate = date.with_timezone(tz);
return newdate.format("%H:%M %d/%m/%y").to_string();
}
}
date.format("%H:%M %d/%m/%y").to_string()
}
You cannot modify the original date variable, because the types won't match. The timezone is part of the type. If you move completely to DateTime<chrono_tz::Tz>, then you could modify the variable, but all your uses of DateTime would need to be changed.

Related

Convert UTC RFC3339 Timestamp to Local Time with the time crate

I've been banging my head against the time crate for the last two days. I can't find, where in their documentation how to take a RFC3339 UTC 2022-12-28T02:11:46Z timestamp and convert that to local time for America/New_York (2022-12-27T21:11:46). I stepped away from using the chrono crate on advise that there is/was a vulnerability and it's not very well maintained as it once was. Chrono also depends on time but in the 0.1.x branch of it.
My cargo.toml includes the line time = { version = "0.3", features = ["macros", "parsing", "local-offset"] } so enable the features I think I need.
use time::{format_description::well_known::Rfc3339, PrimitiveDateTime};
/// The paramater zulu would be a RFC3339 formatted string.
///
/// ```
/// #use time::{format_description::well_known::Rfc3339, PrimitiveDateTime};
/// assert_eq!("2022-12-27T21:11:46", date_time_local("2022-12-28T02:11:46Z".to_string()));
/// ```
fn date_time_local(zulu: &String) -> String {
match PrimitiveDateTime::parse(zulu, &Rfc3339) {
Ok(local) => local.to_string(),
Err(..) => zulu.to_owned(),
}
}
I'm having no such luck here.
fn main() {
assert_eq!("2022-12-27 21:11:46", date_time_local(&"2022-12-28T02:11:46Z".to_string()));
}
/// The parameter zulu should be a RFC3339 formatted string.
/// This converts that Zulu timestamp into a local timestamp.
/// ```
/// assert_eq!("2022-12-27 21:11:46", date_time_local("2022-12-28T02:11:46Z".to_string()));
/// ```
fn date_time_local(zulu: &String) -> String {
use time::{format_description::well_known::Rfc3339, PrimitiveDateTime, UtcOffset};
// Determine Local TimeZone
let utc_offset = match UtcOffset::current_local_offset() {
Ok(utc_offset) => utc_offset,
Err(..) => return zulu.to_owned(),
};
// Parse the given zulu paramater.
let zulu_parsed = match PrimitiveDateTime::parse(zulu, &Rfc3339) {
Ok(zulu_parsed) => zulu_parsed.assume_utc(),
Err(..) => return zulu.to_owned(),
};
// Convert zulu to local time offset.
let parsed = zulu_parsed.to_offset(utc_offset);
format!(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
parsed.year(),
parsed.month() as u8,
parsed.day(),
parsed.hour(),
parsed.minute(),
parsed.second()
)
}
With a slight change that I removed the T between the date and the time.

how to get the timestamp with timezone in rust

I am using this rust code to get the timestamp, but the time without time zone:
use std::time::Duration;
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, TimeZone, Utc};
use diesel::sql_types::Timestamptz;
use rust_wheel::common::util::time_util::get_current_millisecond;
use tokio::time;
#[tokio::main]
async fn main() {
let trigger_time = (get_current_millisecond() - 35000)/1000;
let time_without_zone = NaiveDateTime::from_timestamp( trigger_time ,0);
}
the timestamp result is 2022-08-30 13:00:15, the actual wanted result is: 2022-08-30 21:00:15. Then I tried to set the timezone:
use std::time::Duration;
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, TimeZone, Utc};
use diesel::sql_types::Timestamptz;
use rust_wheel::common::util::time_util::get_current_millisecond;
use tokio::time;
#[tokio::main]
async fn main() {
let trigger_time = (get_current_millisecond() - 35000)/1000;
let time_without_zone = NaiveDateTime::from_timestamp( trigger_time ,0);
let tz_offset = FixedOffset::east(8 * 3600);
let date_time: DateTime<Local> = Local.from_local_datetime(&time_without_zone).unwrap();
print!("{}",date_time);
let dt_with_tz: DateTime<FixedOffset> = tz_offset.from_local_datetime(&time_without_zone).unwrap();
print!("{}",dt_with_tz);
}
the result is 2022-08-30 13:00:15 +08:00. is it possible to get the timestamp with the timezone? what should I do? I mean get the timestamp format like this 2022-08-30 21:00:15.
the result is 2022-08-30 13:00:15 +08:00. is it possible to get the timestamp with the timezone? what should I do? I mean get the timestamp format like this 2022-08-30 21:00:15.
Your timestamp is (I assume) UTC, so that's what you should tell Chrono:
let time_without_zone = NaiveDateTime::from_timestamp(timestamp, 0);
// 2009-02-13 23:31:30
let zoned: DateTime<FixedOffset> = DateTime::from_utc(time_without_zone, FixedOffset::east(8 * 3600));
// 2009-02-14 07:31:30 +08:00
Then you can use naive_local() to get a naive (timezone-less) view of the local time:
zoned.naive_local()
2009-02-14 07:31:30
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=941668e1e10930b0e9a6ede7e79fb0c1
Warning: I'm not entirely sure naive_local() is the correct call, normally in chrono the "local" timezone is the timezone configured for the machine the program runs on (or something along those lines), but for naive_local it seems like chrono just applies the timezone and returns the result as a naive datetime. So it's what you want, but I find it a bit dubious. I can't find a better call tho.

toLocaleString() not converting time to correct string

So I have the below code, which does a bunch of conversions between dates/strings/moments, and it all seems to be converting properly until the line that calls .toLocaleString(), not sure why but it is converting 2022-05-04T17:00:00.000Z to 5/4/2022, 1:00:00 PM, although I believe it should be 5/4/2022, 5:00:00 PM.
let timezone = "Europe/Lisbon"
let currentDate = new Date() // initialize new date object
let localizedTime = moment.tz(currentDate, timezone) // convert it to a moment in the specified timezone
localizedTime.set({ h: 18, m: 0, s: 0 }) // set the hours to 18 (equivalent to 6PM or 18:00)
let postgresFormat = localizedTime.format() // format in a postgres identifiable way
console.log(localizedTime.format())
let convertedBack = moment.tz(postgresFormat, timezone) // convert from string stored in Postgres back to a moment object
console.log(convertedBack)
let localedProperly = convertedBack.toDate() // convert from a moment to a Date object
console.log(localedProperly)
let withLocale = localedProperly.toLocaleString("en-US", { timezone: timezone }) // try to format it in such a way that a client can read it in 'en-US' locale. The local will change dynamically based on the user's device.
console.log(withLocale)
Not sure how to fix this, any help is much appreciated :)

express.js route not recognizing null value for conditional response

I am trying to render an alternative response to my express.js route when a date value in the parameter ends up being invalid. When I see an invalid date the value ends up being null so in my if statement in the middleware I test for a truthy value and if it is not truthy i serve up an alternative response.
What I get is the true value even though the value of the date is null. Here is an example:
api/timestamp/hello is my route.
A valid date should look like this: {"unix":1546214400000,"utc":"Mon, 31 Dec 2018 00:00:00 GMT"}
An invalid date like 'hello' should look like this {'error': 'Invalid Date'}
The code returns the correct value if the date is valid, but if the date is invalid I get {"unix":null,"utc":"Invalid Date"} instead of {'error': 'Invalid Date'}
Below is the code.
app.get('/api/timestamp/:date', (req,res) => {
let date = new Date(req.params.date);
if (date === null) {
res.send({'error': 'Invalid Date'});
} else {
let unix = date.getTime();
let utc = date.toUTCString();
res.send({unix, utc});
}
});
I'm relatively new to express and Node.js for that matter. Any thoughts on why the null value is not being recognized?
Q: Wouldn't it make sense to check for a valid date BEFORE you try converting it to Unix and UTC?
app.get('/api/timestamp/:date', (req,res) => {
let date = new Date(req.params.date);
if (req.params.date && date instanceOf Date) {
let unix = date.getTime();
let utc = date.toUTCString();
res.send({unix, utc});
} else {
res.send({'error': 'Invalid Date'});
}
}
The way you are building your response, node thinks it's a destructuring assignment, therefore you get the strange response.
To get what you want you can make something like this:
app.get('/api/timestamp/:date?', (req,res) => {
if(req.params.date){
let date = new Date(req.params.date);
let unix = date.getTime();
let utc = date.toUTCString();
if(unix) return res.send({unix,utc})
res.send({'error': 'Invalid Date'});
} else
res.send({'error': 'Invalid Date'});
})
Date constructor returns date to 1 January 1970 if the parameter is null and I can't think of any case it would return null.
Which means that your first check will always be false since your are using strict equality.
Probably you better be checking if req.params.date is truthy and unix is a valid timestamp
Hope this helps

moment-timezone: unix timestamps with timezones

i am using momentjs.com with nodejs and im trying to get unix timestamps for multiple timezones, but sadly the the output is not correct.
code:
var moment = require('moment-timezone');
var berlin = moment.tz('Europe/Berlin').unix();
var angeles = moment.tz('America/Los_Angeles').unix();
var london = moment.tz('Europe/London').unix();
console.log(berlin);
console.log(angeles);
console.log(london);
output:
1472241731
1472241731
1472241731
A Unix Timestamp is always UTC based. It is the same timestamp everywhere on the planet simultaneously.
Changing the time zone of a moment object using moment-timezone only affects the local time value, such as used with the format function (and others). It does not change the moment in time being represented, and therefore does not change the underlying timestamp.
you can use Date.parse to get a timestamp, like this
const moment = require("moment-timezone")
const time = '2022-09-02 04:06:25'
var a = new Date(time).valueOf()
var b = Date.parse(moment.tz(time, "America/Chicago").format())
console.log(a) // China 1662062785000
console.log(b) // Chicago 1662109585000
To show correct time to user for different timezone, we can add timezone offset to unix UTC timestamp
const convertToNewTimeZone = (timeInMilliseconds: number, timezone: string) => {
const utcOffset = moment.tz(timeInMilliseconds, timezone).utcOffset();
return moment(timeInMilliseconds)
.add(utcOffset, 'minutes');
};
Note that if you are calculating time in browser, you may have to subtract the browser timezone offset
newTime.clone().subtract(moment().utcOffset(), 'minutes')

Resources