Repository: chronotope/chrono (Rust)

Issue:
Make conversion from NaiveDateTime to DateTime easier
Hello,

I noticed that the conversion from a NaiveDateTime to a DateTime by supplying a TimeZone is not really as easy as it could be (or IDE-friendly).

Mostly, I think the problem is that you have the conversion method on the `TimeZone` trait, instead of on the `NaiveDateTime` object. At least speaking for myself, if I want to convert a NaiveDateTime, I already have one (e.g. because I manually parsed it from somewhere without Time information) and then just want to supply the TimeZone object. As a normal developer, I would just press `.` on the object and hope that my IDE will display me a suitable method.

Therefore, what about something like this?

```rust
impl NaiveDateTime {
    pub fn to_datetime_local<Tz: TimeZone>(&self, tz: Tz) -> LocalResult<DateTime<Tz>> {
        tz.from_local_datetime(self)
    }
}
```

This makes it really obvious how to construct a DateTime<TimeZone> from a NaiveDateTime:

```rust
#[test]
fn test_naivedatetime_to_local() {
    let offset = FixedOffset::east(3600);
    let dt = NaiveDate::from_ymd(2022,05,07).and_hms(14,51,00)
        .to_datetime_local(offset);
    assert_eq!("2022-05-07T14:51:00+01:00", dt.single().unwrap().to_rfc3339());
}
```

Furthermore, while the `LocalResult` is of course correct, I think it would be nice to have some kind of "lossy" output. I would assume that most of the time there is only one result (as the DST transitions are mostly in the night anyway, where they have the least impact). Currently the user has to handle this situation every time, while most of the time the conversion will be correct. Anyway, I think it would be good for a time library to have some sensible default how to work with this, as not every user might know what to do in this case.

I checked on the Java time API, and they handle it like this (https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html#atZone-java.time.ZoneId-):

> In most cases, there is only one valid offset for a local date-time. In the case of an overlap, where clocks are set back, there are two valid offsets. This method uses the earlier offset typically corresponding to "summer".
>
> In the case of a gap, where clocks jump forward, there is no valid offset. Instead, the local date-time is adjusted to be later by the length of the gap. For a typical one hour daylight savings change, the local date-time will be moved one hour later into the offset typically corresponding to "summer".
>
> To obtain the later offset during an overlap, call [ZonedDateTime.withLaterOffsetAtOverlap()](https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html#withLaterOffsetAtOverlap--) on the result of this method. To throw an exception when there is a gap or overlap, use [ZonedDateTime.ofStrict(LocalDateTime, ZoneOffset, ZoneId)](https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html#ofStrict-java.time.LocalDateTime-java.time.ZoneOffset-java.time.ZoneId-).


Why not create an additional method on `LocalResult` which returns something like the Java handling (not quite sure how other time libraries handle this situation)?
 


Discussion:
I'll work on the first issue, but the second part of this should probably be moved to its own issue. Would you like to make a new one? It's a complicated problem that I think would require a breaking change.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b29520ef99..dc3a65642b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,7 @@ Versions with only mechanical changes will be omitted from the following list.
 * Fix panicking when parsing a `DateTime` (@botahamec)
 * Add support for getting week bounds based on a specific `NaiveDate` and a `Weekday` (#666)
 * Remove libc dependency from Cargo.toml.
+* Add the `and_local_timezone` method to `NaiveDateTime`
 
 ## 0.4.19
 
diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs
index 8b28692c42..e2119e002d 100644
--- a/src/naive/datetime/mod.rs
+++ b/src/naive/datetime/mod.rs
@@ -21,7 +21,7 @@ use crate::naive::date::{MAX_DATE, MIN_DATE};
 use crate::naive::time::{MAX_TIME, MIN_TIME};
 use crate::naive::{IsoWeek, NaiveDate, NaiveTime};
 use crate::oldtime::Duration as OldDuration;
-use crate::{Datelike, Timelike, Weekday};
+use crate::{DateTime, Datelike, LocalResult, TimeZone, Timelike, Weekday};
 
 #[cfg(feature = "rustc-serialize")]
 pub(super) mod rustc_serialize;
@@ -722,6 +722,22 @@ impl NaiveDateTime {
     pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat<StrftimeItems<'a>> {
         self.format_with_items(StrftimeItems::new(fmt))
     }
+
+    /// Converts the `NaiveDateTime` into the timezone-aware `DateTime<Tz>`
+    /// with the provided timezone, if possible.
+    ///
+    /// This is experimental and might be removed in the future. Feel free to
+    /// let us know what you think about this API.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use chrono::{NaiveDate, Utc};
+    /// let dt = NaiveDate::from_ymd(2015, 9, 5).and_hms(23, 56, 4).and_local_timezone(Utc).unwrap();
+    /// assert_eq!(dt.timezone(), Utc);
+    pub fn and_local_timezone<Tz: TimeZone>(&self, tz: Tz) -> LocalResult<DateTime<Tz>> {
+        tz.from_local_datetime(self)
+    }
 }
 
 impl Datelike for NaiveDateTime {


Test patch:
diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs
index ea5f91790d..01887332c8 100644
--- a/src/naive/datetime/tests.rs
+++ b/src/naive/datetime/tests.rs
@@ -1,7 +1,7 @@
 use super::NaiveDateTime;
 use crate::naive::{NaiveDate, MAX_DATE, MIN_DATE};
 use crate::oldtime::Duration;
-use crate::Datelike;
+use crate::{Datelike, FixedOffset, Utc};
 use std::i64;
 
 #[test]
@@ -240,3 +240,16 @@ fn test_nanosecond_range() {
         NaiveDateTime::from_timestamp(nanos / A_BILLION, (nanos % A_BILLION) as u32)
     );
 }
+
+#[test]
+fn test_and_timezone() {
+    let ndt = NaiveDate::from_ymd(2022, 6, 15).and_hms(18, 59, 36);
+    let dt_utc = ndt.and_local_timezone(Utc).unwrap();
+    assert_eq!(dt_utc.naive_local(), ndt);
+    assert_eq!(dt_utc.timezone(), Utc);
+
+    let offset_tz = FixedOffset::west(4 * 3600);
+    let dt_offset = ndt.and_local_timezone(offset_tz).unwrap();
+    assert_eq!(dt_offset.naive_local(), ndt);
+    assert_eq!(dt_offset.timezone(), offset_tz);
+}