Skip to content

Commit 4bc0ae1

Browse files
committed
feat!: Represent time as 64 bit integer.
That way, dates beyond 2038 are supported. Further, we rename `Time::seconds_since_unix_epoch` to `seconds`, and remove `seconds()` as there now is a new type, `SecondsSinceUnixEpoch`. In the same vein, we rename `Time::offset_in_seconds` to `offset` as it now has at type `OffsetInSeconds`.
1 parent 2560a2c commit 4bc0ae1

File tree

12 files changed

+126
-93
lines changed

12 files changed

+126
-93
lines changed

gix-date/src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@ pub use parse::function::parse;
2222
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2323
pub struct Time {
2424
/// time in seconds since epoch.
25-
pub seconds_since_unix_epoch: u32,
25+
pub seconds: SecondsSinceUnixEpoch,
2626
/// time offset in seconds, may be negative to match the `sign` field.
27-
pub offset_in_seconds: i32,
27+
pub offset: OffsetInSeconds,
2828
/// the sign of `offset`, used to encode `-0000` which would otherwise loose sign information.
2929
pub sign: time::Sign,
3030
}
31+
32+
/// The amount of seconds since unix epoch.
33+
pub type SecondsSinceUnixEpoch = u64;
34+
/// time offset in seconds.
35+
pub type OffsetInSeconds = i32;

gix-date/src/parse.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub enum Error {
77
RelativeTimeConversion,
88
#[error("Date string can not be parsed")]
99
InvalidDateString { input: String },
10-
#[error("Dates past 2038 can not be represented.")]
10+
#[error("The heat-death of the universe happens before this date")]
1111
InvalidDate(#[from] std::num::TryFromIntError),
1212
#[error("Current time is missing but required to handle relative dates.")]
1313
MissingCurrentTime,
@@ -24,7 +24,7 @@ pub(crate) mod function {
2424
format::{DEFAULT, GITOXIDE, ISO8601, ISO8601_STRICT, SHORT},
2525
Sign,
2626
},
27-
Time,
27+
SecondsSinceUnixEpoch, Time,
2828
};
2929

3030
#[allow(missing_docs)]
@@ -47,7 +47,7 @@ pub(crate) mod function {
4747
Time::new(val.unix_timestamp().try_into()?, val.offset().whole_seconds())
4848
} else if let Ok(val) = OffsetDateTime::parse(input, DEFAULT) {
4949
Time::new(val.unix_timestamp().try_into()?, val.offset().whole_seconds())
50-
} else if let Ok(val) = u32::from_str(input) {
50+
} else if let Ok(val) = SecondsSinceUnixEpoch::from_str(input) {
5151
// Format::Unix
5252
Time::new(val, 0)
5353
} else if let Some(val) = parse_raw(input) {
@@ -60,7 +60,7 @@ pub(crate) mod function {
6060
})
6161
}
6262

63-
fn timestamp(date: OffsetDateTime) -> Result<u32, Error> {
63+
fn timestamp(date: OffsetDateTime) -> Result<SecondsSinceUnixEpoch, Error> {
6464
let timestamp = date.unix_timestamp();
6565
if timestamp < 0 {
6666
Err(Error::TooEarly { timestamp })
@@ -71,7 +71,7 @@ pub(crate) mod function {
7171

7272
fn parse_raw(input: &str) -> Option<Time> {
7373
let mut split = input.split_whitespace();
74-
let seconds_since_unix_epoch: u32 = split.next()?.parse().ok()?;
74+
let seconds: SecondsSinceUnixEpoch = split.next()?.parse().ok()?;
7575
let offset = split.next()?;
7676
if offset.len() != 5 || split.next().is_some() {
7777
return None;
@@ -88,8 +88,8 @@ pub(crate) mod function {
8888
offset_in_seconds *= -1;
8989
};
9090
let time = Time {
91-
seconds_since_unix_epoch,
92-
offset_in_seconds,
91+
seconds,
92+
offset: offset_in_seconds,
9393
sign,
9494
};
9595
Some(time)

gix-date/src/time/format.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,16 @@ impl Time {
7373
.to_time()
7474
.format(&format)
7575
.expect("well-known format into memory never fails"),
76-
Format::Unix => self.seconds_since_unix_epoch.to_string(),
76+
Format::Unix => self.seconds.to_string(),
7777
Format::Raw => self.to_bstring().to_string(),
7878
}
7979
}
8080
}
8181

8282
impl Time {
8383
fn to_time(self) -> time::OffsetDateTime {
84-
time::OffsetDateTime::from_unix_timestamp(self.seconds_since_unix_epoch as i64)
84+
time::OffsetDateTime::from_unix_timestamp(self.seconds as i64)
8585
.expect("always valid unix time")
86-
.to_offset(time::UtcOffset::from_whole_seconds(self.offset_in_seconds).expect("valid offset"))
86+
.to_offset(time::UtcOffset::from_whole_seconds(self.offset).expect("valid offset"))
8787
}
8888
}

gix-date/src/time/init.rs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,68 @@
11
use std::{convert::TryInto, ops::Sub};
22

3-
use crate::{time::Sign, Time};
3+
use crate::{time::Sign, OffsetInSeconds, SecondsSinceUnixEpoch, Time};
44

55
/// Instantiation
66
impl Time {
77
/// Create a new instance from seconds and offset.
8-
pub fn new(seconds_since_unix_epoch: u32, offset_in_seconds: i32) -> Self {
8+
pub fn new(seconds: SecondsSinceUnixEpoch, offset: OffsetInSeconds) -> Self {
99
Time {
10-
seconds_since_unix_epoch,
11-
offset_in_seconds,
12-
sign: offset_in_seconds.into(),
10+
seconds,
11+
offset,
12+
sign: offset.into(),
1313
}
1414
}
1515

1616
/// Return the current time without figuring out a timezone offset
1717
pub fn now_utc() -> Self {
18-
let seconds_since_unix_epoch = time::OffsetDateTime::now_utc()
18+
let seconds = time::OffsetDateTime::now_utc()
1919
.sub(std::time::SystemTime::UNIX_EPOCH)
2020
.whole_seconds()
2121
.try_into()
22-
.expect("this is not year 2038");
22+
.expect("this is not the end of the universe");
2323
Self {
24-
seconds_since_unix_epoch,
25-
offset_in_seconds: 0,
24+
seconds,
25+
offset: 0,
2626
sign: Sign::Plus,
2727
}
2828
}
2929

3030
/// Return the current local time, or `None` if the local time wasn't available.
3131
pub fn now_local() -> Option<Self> {
3232
let now = time::OffsetDateTime::now_utc();
33-
let seconds_since_unix_epoch = now
33+
let seconds = now
3434
.sub(std::time::SystemTime::UNIX_EPOCH)
3535
.whole_seconds()
3636
.try_into()
37-
.expect("this is not year 2038");
37+
.expect("this is not the end of the universe");
3838
// TODO: make this work without cfg(unsound_local_offset), see
3939
// https://github.com/time-rs/time/issues/293#issuecomment-909158529
40-
let offset_in_seconds = time::UtcOffset::local_offset_at(now).ok()?.whole_seconds();
40+
let offset = time::UtcOffset::local_offset_at(now).ok()?.whole_seconds();
4141
Self {
42-
seconds_since_unix_epoch,
43-
offset_in_seconds,
44-
sign: offset_in_seconds.into(),
42+
seconds,
43+
offset,
44+
sign: offset.into(),
4545
}
4646
.into()
4747
}
4848

4949
/// Return the current local time, or the one at UTC if the local time wasn't available.
5050
pub fn now_local_or_utc() -> Self {
5151
let now = time::OffsetDateTime::now_utc();
52-
let seconds_since_unix_epoch = now
52+
let seconds = now
5353
.sub(std::time::SystemTime::UNIX_EPOCH)
5454
.whole_seconds()
5555
.try_into()
56-
.expect("this is not year 2038");
56+
.expect("this is not the end of the universe");
5757
// TODO: make this work without cfg(unsound_local_offset), see
5858
// https://github.com/time-rs/time/issues/293#issuecomment-909158529
59-
let offset_in_seconds = time::UtcOffset::local_offset_at(now)
59+
let offset = time::UtcOffset::local_offset_at(now)
6060
.map(|ofs| ofs.whole_seconds())
6161
.unwrap_or(0);
6262
Self {
63-
seconds_since_unix_epoch,
64-
offset_in_seconds,
65-
sign: offset_in_seconds.into(),
63+
seconds,
64+
offset,
65+
sign: offset.into(),
6666
}
6767
}
6868
}

gix-date/src/time/mod.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@ impl Time {
66
pub fn is_set(&self) -> bool {
77
*self != Self::default()
88
}
9-
10-
/// Return the passed seconds since epoch since this signature was made.
11-
pub fn seconds(&self) -> u32 {
12-
self.seconds_since_unix_epoch
13-
}
149
}
1510

1611
/// Indicates if a number is positive or negative for use in [`Time`].
@@ -58,8 +53,8 @@ mod impls {
5853
impl Default for Time {
5954
fn default() -> Self {
6055
Time {
61-
seconds_since_unix_epoch: 0,
62-
offset_in_seconds: 0,
56+
seconds: 0,
57+
offset: 0,
6358
sign: Sign::Plus,
6459
}
6560
}

gix-date/src/time/write.rs

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl Time {
1414
/// Serialize this instance to `out` in a format suitable for use in header fields of serialized git commits or tags.
1515
pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> {
1616
let mut itoa = itoa::Buffer::new();
17-
out.write_all(itoa.format(self.seconds_since_unix_epoch).as_bytes())?;
17+
out.write_all(itoa.format(self.seconds).as_bytes())?;
1818
out.write_all(b" ")?;
1919
out.write_all(match self.sign {
2020
Sign::Plus => b"+",
@@ -24,7 +24,7 @@ impl Time {
2424
const ZERO: &[u8; 1] = b"0";
2525

2626
const SECONDS_PER_HOUR: i32 = 60 * 60;
27-
let offset = self.offset_in_seconds.abs();
27+
let offset = self.offset.abs();
2828
let hours = offset / SECONDS_PER_HOUR;
2929
assert!(hours < 25, "offset is more than a day: {hours}");
3030
let minutes = (offset - (hours * SECONDS_PER_HOUR)) / 60;
@@ -40,30 +40,48 @@ impl Time {
4040
out.write_all(itoa.format(minutes).as_bytes()).map(|_| ())
4141
}
4242

43-
/// Computes the number of bytes necessary to render this time.
43+
/// Computes the number of bytes necessary to write it using [`Time::write_to()`].
4444
pub fn size(&self) -> usize {
45-
// TODO: this is not year 2038 safe…but we also can't parse larger numbers (or represent them) anyway. It's a trap nonetheless
46-
// that can be fixed by increasing the size to usize.
47-
(if self.seconds_since_unix_epoch >= 1_000_000_000 {
45+
(if self.seconds >= 10_000_000_000_000_000_000 {
46+
20
47+
} else if self.seconds >= 1_000_000_000_000_000_000 {
48+
19
49+
} else if self.seconds >= 100_000_000_000_000_000 {
50+
18
51+
} else if self.seconds >= 10_000_000_000_000_000 {
52+
17
53+
} else if self.seconds >= 1_000_000_000_000_000 {
54+
16
55+
} else if self.seconds >= 100_000_000_000_000 {
56+
15
57+
} else if self.seconds >= 10_000_000_000_000 {
58+
14
59+
} else if self.seconds >= 1_000_000_000_000 {
60+
13
61+
} else if self.seconds >= 100_000_000_000 {
62+
12
63+
} else if self.seconds >= 10_000_000_000 {
64+
11
65+
} else if self.seconds >= 1_000_000_000 {
4866
10
49-
} else if self.seconds_since_unix_epoch >= 100_000_000 {
67+
} else if self.seconds >= 100_000_000 {
5068
9
51-
} else if self.seconds_since_unix_epoch >= 10_000_000 {
69+
} else if self.seconds >= 10_000_000 {
5270
8
53-
} else if self.seconds_since_unix_epoch >= 1_000_000 {
71+
} else if self.seconds >= 1_000_000 {
5472
7
55-
} else if self.seconds_since_unix_epoch >= 100_000 {
73+
} else if self.seconds >= 100_000 {
5674
6
57-
} else if self.seconds_since_unix_epoch >= 10_000 {
75+
} else if self.seconds >= 10_000 {
5876
5
59-
} else if self.seconds_since_unix_epoch >= 1_000 {
77+
} else if self.seconds >= 1_000 {
6078
4
61-
} else if self.seconds_since_unix_epoch >= 100 {
79+
} else if self.seconds >= 100 {
6280
3
63-
} else if self.seconds_since_unix_epoch >= 10 {
81+
} else if self.seconds >= 10 {
6482
2
6583
} else {
6684
1
67-
}) + 2 /*space + sign*/ + 2 /*hours*/ + 2 /*minutes*/
85+
}) + 2 /*space + offset sign*/ + 2 /*offset hours*/ + 2 /*offset minutes*/
6886
}
6987
}

gix-date/tests/fixtures/generate_git_date_baseline.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ baseline '2022-08-17T21:43:13+08:00' 'ISO8601_STRICT'
3838
baseline 'Thu Sep 04 2022 10:45:06 -0400' '' # cannot round-trip, incorrect day-of-week
3939
baseline 'Sun Sep 04 2022 10:45:06 -0400' 'GITOXIDE'
4040
# unix
41-
baseline '123456789' 'UNIX'
41+
baseline '1234567890' 'UNIX'
4242
# raw
4343
baseline '1660874655 +0800' 'RAW'
4444

45+
# Note that we can't necessarily put 64bit dates here yet as `git` on the system might not yet support it.
46+
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:f78af3da4b598ccee56688adf4ff3dcc35a9e002b2b9372d17dd3b342b2bdecd
3-
size 9256
2+
oid sha256:376819eee3c7e23a16abb5ffe4540c215676c183e89c415f0a997b325a0b1331
3+
size 9260

gix-date/tests/time/baseline.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use std::{collections::HashMap, time::SystemTime};
22

33
use gix_date::time::{format, Format};
4+
use gix_date::SecondsSinceUnixEpoch;
45
use once_cell::sync::Lazy;
56

67
type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>;
78

89
struct Sample {
910
format_name: Option<String>,
1011
exit_code: usize,
11-
time_in_seconds_since_unix_epoch: u32,
12+
seconds: SecondsSinceUnixEpoch,
1213
}
1314

1415
static BASELINE: Lazy<HashMap<String, Sample>> = Lazy::new(|| {
@@ -21,7 +22,7 @@ static BASELINE: Lazy<HashMap<String, Sample>> = Lazy::new(|| {
2122
while let Some(date_str) = lines.next() {
2223
let format_name = lines.next().expect("four lines per baseline").to_string();
2324
let exit_code = lines.next().expect("four lines per baseline").parse()?;
24-
let time_in_seconds_since_unix_epoch: u32 = lines
25+
let seconds: SecondsSinceUnixEpoch = lines
2526
.next()
2627
.expect("four lines per baseline")
2728
.parse()
@@ -31,7 +32,7 @@ static BASELINE: Lazy<HashMap<String, Sample>> = Lazy::new(|| {
3132
Sample {
3233
format_name: (!format_name.is_empty()).then_some(format_name),
3334
exit_code,
34-
time_in_seconds_since_unix_epoch,
35+
seconds,
3536
},
3637
);
3738
}
@@ -47,7 +48,7 @@ fn parse_compare_format() {
4748
Sample {
4849
format_name,
4950
exit_code,
50-
time_in_seconds_since_unix_epoch,
51+
seconds: time_in_seconds_since_unix_epoch,
5152
},
5253
) in BASELINE.iter()
5354
{
@@ -58,7 +59,7 @@ fn parse_compare_format() {
5859
"{pattern:?} disagrees with baseline: {res:?}"
5960
);
6061
if let Ok(t) = res {
61-
let actual = t.seconds_since_unix_epoch;
62+
let actual = t.seconds;
6263
assert_eq!(
6364
actual, *time_in_seconds_since_unix_epoch,
6465
"{pattern:?} disagrees with baseline seconds since epoch: {actual:?}"

gix-date/tests/time/format.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,16 @@ fn custom_compile_time() {
8282

8383
fn time() -> Time {
8484
Time {
85-
seconds_since_unix_epoch: 123456789,
86-
offset_in_seconds: 9000,
85+
seconds: 123456789,
86+
offset: 9000,
8787
sign: Sign::Plus,
8888
}
8989
}
9090

9191
fn time_dec1() -> Time {
9292
Time {
93-
seconds_since_unix_epoch: 123543189,
94-
offset_in_seconds: 9000,
93+
seconds: 123543189,
94+
offset: 9000,
9595
sign: Sign::Plus,
9696
}
9797
}

0 commit comments

Comments
 (0)