From c28051020ada044a6dfc8e78d5f075b915c108a3 Mon Sep 17 00:00:00 2001 From: David 'Epper' Marshall Date: Sun, 15 May 2022 03:55:24 -0400 Subject: [PATCH] time: fix calculate_date_from_offset (#14399) --- vlib/time/parse_test.v | 14 ++++++ vlib/time/time.v | 15 +++--- vlib/time/time_addition_test.v | 4 +- vlib/time/time_test.v | 6 +++ vlib/time/unix.v | 84 ++++++++++++---------------------- 5 files changed, 58 insertions(+), 65 deletions(-) diff --git a/vlib/time/parse_test.v b/vlib/time/parse_test.v index f544e4f95d..1fd6574d47 100644 --- a/vlib/time/parse_test.v +++ b/vlib/time/parse_test.v @@ -184,3 +184,17 @@ fn test_parse_rfc3339() { assert expected == output } } + +fn test_ad_second_to_parse_result_in_2001() ? { + now_tm := time.parse('2001-01-01 04:00:00')? + future_tm := now_tm.add_seconds(60) + assert future_tm.str() == '2001-01-01 04:01:00' + assert now_tm.unix < future_tm.unix +} + +fn test_ad_second_to_parse_result_pre_2001() ? { + now_tm := time.parse('2000-01-01 04:00:00')? + future_tm := now_tm.add_seconds(60) + assert future_tm.str() == '2000-01-01 04:01:00' + assert now_tm.unix < future_tm.unix +} diff --git a/vlib/time/time.v b/vlib/time/time.v index 7f9c3f6c32..744dfc3296 100644 --- a/vlib/time/time.v +++ b/vlib/time/time.v @@ -16,9 +16,10 @@ pub const ( seconds_per_hour = 60 * seconds_per_minute seconds_per_day = 24 * seconds_per_hour seconds_per_week = 7 * seconds_per_day - days_per_400_years = 365 * 400 + 97 - days_per_100_years = 365 * 100 + 24 - days_per_4_years = 365 * 4 + 1 + days_per_400_years = days_in_year * 400 + 97 + days_per_100_years = days_in_year * 100 + 24 + days_per_4_years = days_in_year * 4 + 1 + days_in_year = 365 days_before = [ 0, 31, @@ -179,13 +180,13 @@ pub fn (t Time) relative() string { } return '$prefix$d days$suffix' } - if secs < time.seconds_per_hour * 24 * 365 { + if secs < time.seconds_per_hour * 24 * time.days_in_year { if prefix == 'in ' { return 'on $t.md()' } return 'last $t.md()' } - y := secs / time.seconds_per_hour / 24 / 365 + y := secs / time.seconds_per_hour / 24 / time.days_in_year if y == 1 { return '${prefix}1 year$suffix' } @@ -234,14 +235,14 @@ pub fn (t Time) relative_short() string { } return '$prefix${h}h$suffix' } - if secs < time.seconds_per_hour * 24 * 365 { + if secs < time.seconds_per_hour * 24 * time.days_in_year { d := secs / time.seconds_per_hour / 24 if d == 1 { return '${prefix}1d$suffix' } return '$prefix${d}d$suffix' } - y := secs / time.seconds_per_hour / 24 / 365 + y := secs / time.seconds_per_hour / 24 / time.days_in_year if y == 1 { return '${prefix}1y$suffix' } diff --git a/vlib/time/time_addition_test.v b/vlib/time/time_addition_test.v index d21d5aed0e..00a3c3dae3 100644 --- a/vlib/time/time_addition_test.v +++ b/vlib/time/time_addition_test.v @@ -5,13 +5,13 @@ fn test_add_to_day_in_the_previous_century() ? { aa := a.add_days(180) dump(a.debug()) dump(aa.debug()) - assert aa.ymmdd() == '1900-06-29' + assert aa.ymmdd() == '1900-06-30' } fn test_add_to_day_in_the_past() ? { a := time.parse_iso8601('1990-03-01')? aa := a.add_days(180) - assert aa.ymmdd() == '1990-08-27' + assert aa.ymmdd() == '1990-08-28' } fn test_add_to_day_in_the_recent_past() ? { diff --git a/vlib/time/time_test.v b/vlib/time/time_test.v index dc9e1b1516..bf26df5ba5 100644 --- a/vlib/time/time_test.v +++ b/vlib/time/time_test.v @@ -267,3 +267,9 @@ fn test_recursive_local_call() { fn test_strftime() { assert '1980 July 11' == time_to_test.strftime('%Y %B %d') } + +fn test_add_seconds_to_time() { + now_tm := time.now() + future_tm := now_tm.add_seconds(60) + assert now_tm.unix < future_tm.unix +} diff --git a/vlib/time/unix.v b/vlib/time/unix.v index a20c1a0682..84850875ed 100644 --- a/vlib/time/unix.v +++ b/vlib/time/unix.v @@ -48,67 +48,39 @@ pub fn unix2(abs i64, microsecond int) Time { fn calculate_date_from_offset(day_offset_ i64) (int, int, int) { mut day_offset := day_offset_ - // Move offset to year 2001 as it's the start of a new 400-year cycle - // Code below this rely on the fact that the day_offset is lined up with the 400-year cycle - // 1970-2000 (inclusive) has 31 years (8 of which are leap years) - mut year := 2001 - day_offset -= 31 * 365 + 8 - // Account for 400 year cycle - year += int(day_offset / days_per_400_years) * 400 - day_offset %= days_per_400_years - // Account for 100 year cycle - if day_offset == days_per_100_years * 4 { - year += 300 - day_offset -= days_per_100_years * 3 + + // source: http://howardhinnant.github.io/date_algorithms.html#civil_from_days + + // shift from 1970-01-01 to 0000-03-01 + day_offset += 719468 // int(days_per_400_years * 1970 / 400 - (28+31)) + + mut era := 0 + if day_offset >= 0 { + era = int(day_offset / days_per_400_years) } else { - year += int(day_offset / days_per_100_years) * 100 - day_offset %= days_per_100_years + era = int((day_offset - days_per_400_years - 1) / days_per_400_years) } - // Account for 4 year cycle - if day_offset == days_per_4_years * 25 { - year += 96 - day_offset -= days_per_4_years * 24 + // doe(day of era) [0, 146096] + doe := day_offset - era * days_per_400_years + // yoe(year of era) [0, 399] + yoe := (doe - doe / (days_per_4_years - 1) + doe / days_per_100_years - doe / (days_per_400_years - 1)) / days_in_year + // year number + mut y := int(yoe + era * 400) + // doy (day of year), with year beginning Mar 1 [0, 365] + doy := doe - (days_in_year * yoe + yoe / 4 - yoe / 100) + + mp := (5 * doy + 2) / 153 + d := int(doy - (153 * mp + 2) / 5 + 1) + mut m := int(mp) + if mp < 10 { + m += 3 } else { - year += int(day_offset / days_per_4_years) * 4 - day_offset %= days_per_4_years + m -= 9 } - // Account for every year - if day_offset == 365 * 4 { - year += 3 - day_offset -= 365 * 3 - } else { - year += int(day_offset / 365) - day_offset %= 365 + if m <= 2 { + y += 1 } - if day_offset < 0 { - year-- - if is_leap_year(year) { - day_offset += 366 - } else { - day_offset += 365 - } - } - if is_leap_year(year) { - if day_offset > 31 + 29 - 1 { - // After leap day; pretend it wasn't there. - day_offset-- - } else if day_offset == 31 + 29 - 1 { - // Leap day. - return year, 2, 29 - } - } - mut estimated_month := day_offset / 31 - for day_offset >= days_before[estimated_month + 1] { - estimated_month++ - } - for day_offset < days_before[estimated_month] { - if estimated_month == 0 { - break - } - estimated_month-- - } - day_offset -= days_before[estimated_month] - return year, int(estimated_month + 1), int(day_offset + 1) + return y, m, d } fn calculate_time_from_offset(second_offset_ i64) (int, int, int) {