gabotechs commented on code in PR #23542:
URL: https://github.com/apache/datafusion/pull/23542#discussion_r3579418030


##########
datafusion/functions/src/datetime/date_trunc.rs:
##########
@@ -591,52 +591,128 @@ where
 
 fn _date_trunc_coarse_with_tz(
     granularity: DateTruncGranularity,
-    value: Option<DateTime<Tz>>,
+    value: DateTime<Tz>,
 ) -> Result<Option<i64>> {
-    if let Some(value) = value {
-        let local = value.naive_local();
-        let truncated = _date_trunc_coarse::<NaiveDateTime>(granularity, 
Some(local))?;
-        let truncated = truncated.and_then(|truncated| {
-            match truncated.and_local_timezone(value.timezone()) {
-                LocalResult::None => {
-                    // This can happen if the date_trunc operation moves the 
time into
-                    // an hour that doesn't exist due to daylight savings. On 
known example where
-                    // this can happen is with historic dates in the 
America/Sao_Paulo time zone.
-                    // To account for this adjust the time by a few hours, 
convert to local time,
-                    // and then adjust the time back.
-                    truncated
-                        .sub(TimeDelta::try_hours(3).unwrap())
-                        .and_local_timezone(value.timezone())
-                        .single()
-                        .map(|v| v.add(TimeDelta::try_hours(3).unwrap()))
-                }
-                LocalResult::Single(datetime) => Some(datetime),
-                LocalResult::Ambiguous(datetime1, datetime2) => {
-                    // Because we are truncating from an equally or more 
specific time
-                    // the original time must have been within the ambiguous 
local time
-                    // period. Therefore the offset of one of these times 
should match the
-                    // offset of the original time.
-                    if datetime1.offset().fix() == value.offset().fix() {
-                        Some(datetime1)
-                    } else {
-                        Some(datetime2)
-                    }
+    let local = value.naive_local();
+    let truncated = _date_trunc_coarse::<NaiveDateTime>(granularity, 
Some(local))?;
+    let truncated = truncated.and_then(|truncated| {
+        match truncated.and_local_timezone(value.timezone()) {
+            LocalResult::None => {
+                // This can happen if the date_trunc operation moves the time 
into
+                // an hour that doesn't exist due to daylight savings. On 
known example where
+                // this can happen is with historic dates in the 
America/Sao_Paulo time zone.
+                // To account for this adjust the time by a few hours, convert 
to local time,
+                // and then adjust the time back.
+                truncated
+                    .sub(TimeDelta::try_hours(3).unwrap())
+                    .and_local_timezone(value.timezone())
+                    .single()
+                    .map(|v| v.add(TimeDelta::try_hours(3).unwrap()))
+            }
+            LocalResult::Single(datetime) => Some(datetime),
+            LocalResult::Ambiguous(datetime1, datetime2) => {
+                // Because we are truncating from an equally or more specific 
time
+                // the original time must have been within the ambiguous local 
time
+                // period. Therefore the offset of one of these times should 
match the
+                // offset of the original time.
+                if datetime1.offset().fix() == value.offset().fix() {
+                    Some(datetime1)
+                } else {
+                    Some(datetime2)
                 }
             }
-        });
-        Ok(truncated.and_then(|value| value.timestamp_nanos_opt()))
+        }
+    });
+    Ok(truncated.and_then(|value| value.timestamp_nanos_opt()))
+}
+
+/// Days from the Unix epoch to 0000-03-01, the epoch used by the civil 
calendar
+/// conversions below.
+const DAYS_EPOCH_SHIFT: i64 = 719_468;
+
+/// Days in a 400 year era of the proleptic Gregorian calendar.
+const DAYS_PER_ERA: i64 = 146_097;
+
+/// Splits a day count relative to the Unix epoch into a proleptic Gregorian
+/// year, month (1-12) and day of month (1-31).
+fn civil_from_days(days: i64) -> (i64, i64, i64) {
+    let z = days + DAYS_EPOCH_SHIFT;
+    let era = z.div_euclid(DAYS_PER_ERA);
+    let day_of_era = z.rem_euclid(DAYS_PER_ERA);
+    let year_of_era = (day_of_era - day_of_era / 1460 + day_of_era / 36524
+        - day_of_era / 146_096)
+        / 365;
+    let day_of_year =
+        day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
+    // Month index with March as 0, so that the leap day falls at the end of 
the year.
+    let month_index = (5 * day_of_year + 2) / 153;
+    let day = day_of_year - (153 * month_index + 2) / 5 + 1;
+    let month = if month_index < 10 {
+        month_index + 3
     } else {
-        _date_trunc_coarse::<NaiveDateTime>(granularity, None)?;
-        Ok(None)
-    }
+        month_index - 9
+    };
+    let year = year_of_era + era * 400 + i64::from(month <= 2);
+    (year, month, day)
 }
 

Review Comment:
   This math looks correct. Is there any link we can put in the comment so that 
people and robots can have a reference in case this code needs some maintenance?
   
   For example, I found these:
   - https://howardhinnant.github.io/date_algorithms.html#days_from_civil
   - https://howardhinnant.github.io/date_algorithms.html#civil_from_days
   
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to