24

I am parsing dates and times in Rust using the chrono crate. The dates and times are from a website in which the date and time are from different sections of the page.

The date is shown in the format %d/%m/%Y (example: 27/08/2018). The time is shown with only the hour (example: 12, 10, 21, etc.)

I want to store these datetimes as UTC so that I can compute time remaining until a given datetime from now in a "timezone agnostic" way. I know which timezone these datetimes are from (Paris time).

I created a NaiveDate from the date input (this is a work in progress so there's no error handling yet):

let naive_date = NaiveDate::parse_from_str(date, "%d/%m/%Y").unwrap()

From that point on, what would be the best way to get the UTC DateTime, given that I have a string with the hour?

I am lost in the various TimeZone/Offset traits, and do not know if I should use a Local, or FixedOffset and then convert to Utc.

E_net4
  • 27,810
  • 13
  • 101
  • 139
Justmaker
  • 1,261
  • 2
  • 11
  • 20
  • "The date is shown in the following format: %d/%m/%Y (example: 27/08/2018). Time is only an hour (12, 10, 21, etc.)", Where is the hours in "27/08/2018" ? – Stargateur May 13 '18 at 08:28
  • I get the hour in another element. So I parse two strings, one to get the date, one to get the hour. I would like to avoid building a fake datetime string, and specify which time zone the datetime is from. – Justmaker May 13 '18 at 10:22
  • I am noticing an incoherence. Are all date-time instances in the same time zone or not? Please update the question instead of keeping useful information in the comments. – E_net4 May 13 '18 at 11:03
  • Your question don't make sense, `NaiveDate` is already without timezone, this is the first thing write in the doc. `naive_date.and_hms(hour, 0, 0);` ???? – Stargateur May 13 '18 at 11:55
  • 1
    I think I understand the question. You have a time and date, which are both naive - they don't have timezones attached to them. But, you _do_ happen to know that the timezone is, for example, +0100. You now want to create a `DateTime`, properly adjusted for the timezone. Is that about right? – Peter Hall May 13 '18 at 16:36
  • @PeterHall exactly – Justmaker May 13 '18 at 17:42
  • @Stargateur What I meant is that I want to have the datetime with the timezone, but adjusted to UTC. So for example, let's say my date element is "27/08/2018" and the hour element is "12". I know the timezone is Paris (currently UTC + 2). So, I want to create a DateTime from these info. Sorry if this wasn't clear... – Justmaker May 13 '18 at 17:44

3 Answers3

26

The Chrono documentation could probably be improved to make it easier to find how to do these things.

Assuming this is your starting point:

use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};

// The date you parsed
let date = NaiveDate::from_ymd(2018, 5, 13);
// The known 1 hour time offset in seconds
let tz_offset = FixedOffset::east(1 * 3600);
// The known time
let time = NaiveTime::from_hms(17, 0, 0);
// Naive date time, with no time zone information
let datetime = NaiveDateTime::new(date, time);

You can then use the FixedOffset to construct a DateTime:

let dt_with_tz: DateTime<FixedOffset> = tz_offset.from_local_datetime(&datetime).unwrap();

If you need to convert it to a DateTime<Utc>, you can do this:

let dt_with_tz_utc: DateTime<Utc> = Utc.from_utc_datetime(&dt_with_tz.naive_utc());
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Then it's exactly what I needed, thank you very much for your answer. – Justmaker May 13 '18 at 20:19
  • For information, using chrono-tz is much more confortable: pub fn create_date_time_from_paris(date: NaiveDate, time: NaiveTime) -> DateTime { let naive_datetime = NaiveDateTime::new(date, time); let paris_time = Paris.from_local_datetime(&naive_datetime).unwrap(); paris_time.with_timezone(&Utc) } – Justmaker May 14 '18 at 10:28
  • @Justmaker Thanks, I learnt something too. In looking at the documentation from chrono-tz, I realised you can do the same thing from a `FixedOffset`, which you already had. I updated my answer with that, since it's much better than subtracting the offsets. – Peter Hall May 14 '18 at 10:50
  • Yes, but then you'd need to handle daylight saving manually, whereas chrono-tz handles it for you ;) – Justmaker May 14 '18 at 12:13
  • 3
    @Justmaker It kind of depends what information you have. If you know the current timezone is UTC+0100 then this is fine. If you know you are in Paris, then `chrono-tz` seems better. I'm still not sure how you would guarantee to pick the correct timezone for an arbitrary city though? – Peter Hall May 14 '18 at 12:16
  • In my use case, I don't need to worry about this, the dates are known to be from Paris. My main worry is comparing the user's (who could be anywhere) datetime to that Paris one. For picking the correct timezone for an arbitrary city, I don't think it can be done without having the information. You'd rely on your front or other means to give you the date time + timezone. – Justmaker May 14 '18 at 12:19
  • I'm confused how adding a fixed offset takes into account daylight time savings in France. So isn't this bound to break or does offset somehow do that behind the scenes? – Brecht De Rooms Jun 30 '23 at 08:24
16

I've discovered chrono-tz and found it much easier to use. For example:

pub fn create_date_time_from_paris(date: NaiveDate, time: NaiveTime) -> DateTime<Utc> {
    let naive_datetime = NaiveDateTime::new(date, time);
    let paris_time = Paris.from_local_datetime(&naive_datetime).unwrap();
    paris_time.with_timezone(&Utc)
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Justmaker
  • 1,261
  • 2
  • 11
  • 20
1

The dates and times are from a website in which the date and time are from different sections of the page.

Here's an example of how you can incrementally parse multiple values from distinct strings, provide default values for unparsed information, and use Chrono's built-in timezone conversion.

The key is to use the parse function to update a Parsed struct. You can use the StrftimeItems iterator to continue to use more readable format strings.

extern crate chrono;

use chrono::prelude::*;

fn example(date: &str, hour: &str) -> chrono::ParseResult<DateTime<Utc>> {
    use chrono::format::{self, strftime::StrftimeItems, Parsed};

    // Set up a struct to perform successive parsing into
    let mut p = Parsed::default();

    // Parse the date information
    format::parse(&mut p, date.trim(), StrftimeItems::new("%d/%m/%Y"))?;
    // Parse the time information and provide default values we don't parse
    format::parse(&mut p, hour.trim(), StrftimeItems::new("%H"))?;
    p.minute = Some(0);
    p.second = Some(0);

    // Convert parsed information into a DateTime in the Paris timezone
    let paris_time_zone_offset = FixedOffset::east(1 * 3600);
    let dt = p.to_datetime_with_timezone(&paris_time_zone_offset)?;

    // You can also use chrono-tz instead of hardcoding it
    // let dt = p.to_datetime_with_timezone(&chrono_tz::Europe::Paris)?;

    // Convert to UTC
    Ok(dt.with_timezone(&Utc))
}

fn main() {
    let date = "27/08/2018";
    let hour = "12";

    println!("dt = {:?}", example(date, hour)); // Ok(2018-08-27T11:00:00Z)
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Could be `p.offset = Some(1 * 3600);` or `format::parse(&mut p, timezone.trim(), StrftimeItems::new("%z"))?;` – Stargateur May 14 '18 at 00:20