diff --git a/lib/std/time/datetime.c3 b/lib/std/time/datetime.c3 index 1eb9b84ec..247242137 100644 --- a/lib/std/time/datetime.c3 +++ b/lib/std/time/datetime.c3 @@ -20,6 +20,19 @@ fn DateTime from_date(int year, Month month = JANUARY, int day = 1, int hour = 0 return dt; } +/** + * @require day >= 1 && day < 32 + * @require hour >= 0 && hour < 24 + * @require min >= 0 && min < 60 + * @require sec >= 0 && sec < 60 + * @require us >= 0 && us < 999_999 + * @require gmt_offset >= -12 * 3600 && gmt_offset <= 14 * 3600 + **/ +fn TzDateTime from_date_tz(int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0, int gmt_offset = 0) +{ + return from_date(year, month, day, hour, min, sec, us).with_gmt_offset(gmt_offset); +} + fn TzDateTime DateTime.to_local(&self) { Tm tm @noinit; @@ -47,6 +60,57 @@ fn TzDateTime DateTime.to_local(&self) return dt; } +/** + * Update timestamp to gmt_offset while keeping the date and time + * values unchanged. + * + * @require gmt_offset >= -12 * 3600 && gmt_offset <= 14 * 3600 + **/ +fn TzDateTime DateTime.with_gmt_offset(self, int gmt_offset) +{ + TzDateTime dt = { self, 0 }; + return dt.with_gmt_offset(gmt_offset); +} + +/** + * Update timestamp to gmt_offset while keeping the date and time + * values unchanged. + * + * @require gmt_offset >= -12 * 3600 && gmt_offset <= 14 * 3600 + **/ +fn TzDateTime TzDateTime.with_gmt_offset(self, int gmt_offset) +{ + self.time -= (Time)(gmt_offset - self.gmt_offset) * (Time)time::SEC; + return { self.date_time, gmt_offset }; +} + +/** + * Update the date and time values to gmt_offset while keeping the + * timestamp unchanged. + * + * @require gmt_offset >= -12 * 3600 && gmt_offset <= 14 * 3600 + * @ensure self.time == return.time + **/ +fn TzDateTime DateTime.to_gmt_offset(self, int gmt_offset) +{ + TzDateTime dt = { self, 0 }; + return dt.to_gmt_offset(gmt_offset); +} + +/** + * Update the date and time values to gmt_offset while keeping the + * timestamp unchanged. + * + * @require gmt_offset >= -12 * 3600 && gmt_offset <= 14 * 3600 + * @ensure self.time == return.time + **/ +fn TzDateTime TzDateTime.to_gmt_offset(self, int gmt_offset) { + Time time = self.time + (Time)(gmt_offset - self.gmt_offset) * (Time)time::SEC; + DateTime dt = from_time(time); + dt.time = self.time; + return { dt, gmt_offset }; +} + /** * @require day >= 1 && day < 32 * @require hour >= 0 && hour < 24 @@ -121,6 +185,16 @@ fn DateTime DateTime.add_months(&self, int months) return from_date(year, (Month)month, self.day, self.hour, self.min, self.sec, self.usec); } + +fn TzDateTime TzDateTime.add_seconds(&self, int seconds) => self.date_time.add_seconds(seconds).to_gmt_offset(self.gmt_offset); +fn TzDateTime TzDateTime.add_minutes(&self, int minutes) => self.date_time.add_minutes(minutes).to_gmt_offset(self.gmt_offset); +fn TzDateTime TzDateTime.add_hours(&self, int hours) => self.date_time.add_hours(hours).to_gmt_offset(self.gmt_offset); +fn TzDateTime TzDateTime.add_days(&self, int days) => self.date_time.add_days(days).to_gmt_offset(self.gmt_offset); +fn TzDateTime TzDateTime.add_weeks(&self, int weeks) => self.date_time.add_weeks(weeks).to_gmt_offset(self.gmt_offset); + +fn TzDateTime TzDateTime.add_years(&self, int years) => self.date_time.add_years(years).with_gmt_offset(self.gmt_offset); +fn TzDateTime TzDateTime.add_months(&self, int months) => self.date_time.add_months(months).with_gmt_offset(self.gmt_offset); + fn DateTime from_time(Time time) { DateTime dt @noinit; @@ -128,6 +202,15 @@ fn DateTime from_time(Time time) return dt; } +/** + * @require gmt_offset >= -12 * 3600 && gmt_offset <= 14 * 3600 + * @ensure time == return.time + **/ +fn TzDateTime from_time_tz(Time time, int gmt_offset) +{ + return from_time(time).to_gmt_offset(gmt_offset); +} + fn Time DateTime.to_time(&self) @inline { return self.time; @@ -167,4 +250,4 @@ fn double DateTime.diff_sec(self, DateTime from) fn Duration DateTime.diff_us(self, DateTime from) { return self.time.diff_us(from.time); -} \ No newline at end of file +} diff --git a/releasenotes.md b/releasenotes.md index e4b1e9d62..6e0029a86 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -70,6 +70,7 @@ - Add `io::read_new_fully` for reading to the end of a stream. - Add `io::wrap_bytes` for reading bytes with `io` functions. - Add `rnd` and `rand_in_range` default random functions. +- Additional timezone related functions for `datetime`. ## 0.6.2 Change list diff --git a/test/unit/stdlib/time/datetime.c3 b/test/unit/stdlib/time/datetime.c3 index c5820b702..315e1b5ea 100644 --- a/test/unit/stdlib/time/datetime.c3 +++ b/test/unit/stdlib/time/datetime.c3 @@ -42,4 +42,89 @@ DateTime d = datetime::from_date(1973, APRIL, 27); x = d.add_months(0); assert(x.year == 1975); assert(x.month == MAY); -} \ No newline at end of file +} + +fn void test_timezone() +{ + int offset_hours = 7; + int offset = offset_hours * 3600; + + DateTime d1 = datetime::from_date(2022, OCTOBER, 15); + TzDateTime d2 = datetime::from_date_tz(2022, OCTOBER, 15, gmt_offset: offset); + + DateTime dt; + TzDateTime tz; + + Time t1 = d1.time; + Time t2 = d2.time; + + assert(t1 == 1665792000000000); + assert(t2 == 1665766800000000); + + // to_gmt_offset should keep the timesampt value + tz = d1.to_gmt_offset(offset); + assert(tz.time == t1); + assert(tz.year == 2022); + assert(tz.month == OCTOBER); + assert(tz.day == 15); + assert(tz.hour == offset_hours); + assert(tz.min == 0); + assert(tz.gmt_offset == offset); + + // with_gmt_offset should keep the date/time values and adjust the timestamp + tz = d1.with_gmt_offset(offset); + assert(tz.time == t2); + assert(tz.year == d1.year); + assert(tz.month == d1.month); + assert(tz.day == d1.day); + assert(tz.hour == d1.hour); + assert(tz.min == d1.min); + assert(tz.gmt_offset == offset); + + dt = datetime::from_time(t1); + assert(dt.day == 15); + assert(dt.hour == 0); + dt = datetime::from_time(t2); + assert(dt.day == 14); + assert(dt.hour == 24 - offset_hours); + + // from_time_tz should keep the timesampt value + tz = datetime::from_time_tz(t1, offset); + assert(tz.time == t1); + assert(tz.day == 15); + assert(tz.hour == offset_hours); + assert(tz.gmt_offset == offset); + tz = datetime::from_time_tz(t2, offset); + assert(tz.time == t2); + assert(tz.day == 15); + assert(tz.hour == 0); + assert(tz.gmt_offset == offset); + + // The add_* methods should adjust the targeted date/time values while + // keeping the others unchanged. The gmt_offset should be kept as well. + dt = d1.add_hours(1); + assert(dt.day == 15); + assert(dt.hour == 1); + tz = d2.add_hours(1); + assert(tz.day == 15); + assert(tz.hour == 1); + assert(tz.gmt_offset == offset); + + dt = d1.add_days(1); + assert(dt.day == 16); + assert(dt.hour == 0); + tz = d2.add_days(1); + assert(tz.day == 16); + assert(tz.hour == 0); + assert(tz.gmt_offset == offset); + + dt = d1.add_months(1); + assert(dt.month == NOVEMBER); + assert(dt.day == 15); + assert(dt.hour == 0); + tz = d2.add_months(1); + assert(tz.month == NOVEMBER); + assert(tz.day == 15); + assert(tz.hour == 0); + assert(tz.gmt_offset == offset); +}