mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Date/Time formatters (#1782)
* Add .DS_Store to .gitignore * Allow <= 999_999 as usec on DateTime (was < 999_999) * Move [Tz]DateTime .format() to std::time::datetime and import only with libc * Changed name to DateTimeFormat, prefer function over method. Move names to enum. * Updated tests to the latest standard. --------- Co-authored-by: Christoffer Lerno <christoffer@aegik.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -79,3 +79,5 @@ TAGS
|
||||
# 'nix build' resulting symlink
|
||||
result
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
@@ -11,7 +11,7 @@ fn DateTime now()
|
||||
@require hour >= 0 && hour < 24
|
||||
@require min >= 0 && min < 60
|
||||
@require sec >= 0 && sec < 60
|
||||
@require us >= 0 && us < 999_999
|
||||
@require us >= 0 && us <= 999_999
|
||||
*>
|
||||
fn DateTime from_date(int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0)
|
||||
{
|
||||
@@ -25,7 +25,7 @@ fn DateTime from_date(int year, Month month = JANUARY, int day = 1, int hour = 0
|
||||
@require hour >= 0 && hour < 24
|
||||
@require min >= 0 && min < 60
|
||||
@require sec >= 0 && sec < 60
|
||||
@require us >= 0 && us < 999_999
|
||||
@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)
|
||||
@@ -117,7 +117,7 @@ fn TzDateTime TzDateTime.to_gmt_offset(self, int gmt_offset) {
|
||||
@require hour >= 0 && hour < 24
|
||||
@require min >= 0 && min <= 60
|
||||
@require sec >= 0 && sec < 60
|
||||
@require us >= 0 && us < 999_999
|
||||
@require us >= 0 && us <= 999_999
|
||||
*>
|
||||
fn void DateTime.set_date(&self, int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0)
|
||||
{
|
||||
|
||||
95
lib/std/time/format.c3
Normal file
95
lib/std/time/format.c3
Normal file
@@ -0,0 +1,95 @@
|
||||
module std::time::datetime @if(env::LIBC);
|
||||
|
||||
|
||||
enum DateTimeFormat
|
||||
{
|
||||
ANSIC, // "Mon Jan _2 15:04:05 2006"
|
||||
UNIXDATE, // "Mon Jan _2 15:04:05 GMT 2006"
|
||||
RUBYDATE, // "Mon Jan 02 15:04:05 -0700 2006"
|
||||
RFC822, // "02 Jan 06 15:04 GMT"
|
||||
RFC822Z, // "02 Jan 06 15:04 -0700"
|
||||
RFC850, // "Monday, 02-Jan-06 15:04:05 GMT"
|
||||
RFC1123, // "Mon, 02 Jan 2006 15:04:05 GMT"
|
||||
RFC1123Z, // "Mon, 02 Jan 2006 15:04:05 -0700"
|
||||
RFC3339, // "2006-01-02T15:04:05Z"
|
||||
RFC3339Z, // "2006-01-02T15:04:05+07:00"
|
||||
RFC3339MS, // "2006-01-02T15:04:05.999999Z"
|
||||
RFC3339ZMS, // "2006-01-02T15:04:05.999999+07:00"
|
||||
DATETIME, // "2006-01-02 15:04:05"
|
||||
DATEONLY, // "2006-01-02"
|
||||
TIMEONLY, // "15:04:05"
|
||||
}
|
||||
|
||||
fn String format(DateTimeFormat type, TzDateTime dt, Allocator allocator)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ANSIC:
|
||||
return string::format("%s %s %2d %02d:%02d:%02d %04d", dt.weekday.abbrev, dt.month.abbrev, dt.day, dt.hour, dt.min, dt.sec, dt.year, allocator: allocator);
|
||||
case UNIXDATE:
|
||||
return string::format("%s %s %2d %02d:%02d:%02d GMT %04d", dt.weekday.abbrev, dt.month.abbrev, dt.day, dt.hour, dt.min, dt.sec, dt.year, allocator: allocator);
|
||||
case RUBYDATE:
|
||||
return string::format("%s %s %2d %02d:%02d:%02d %s %04d", dt.weekday.abbrev, dt.month.abbrev, dt.day, dt.hour, dt.min, dt.sec, temp_numeric_tzsuffix(dt.gmt_offset), dt.year, allocator: allocator);
|
||||
case RFC822:
|
||||
dt = dt.to_gmt_offset(0); // For named representations of the timezone we always go for GMT, which is required by some RFCs
|
||||
return string::format("%02d %s %02d %02d:%02d GMT", dt.day, dt.month.abbrev, dt.year % 100, dt.hour, dt.min, allocator: allocator);
|
||||
case RFC822Z:
|
||||
return string::format("%02d %s %02d %02d:%02d %s", dt.day, dt.month.abbrev, dt.year % 100, dt.hour, dt.min, temp_numeric_tzsuffix(dt.gmt_offset), allocator: allocator);
|
||||
case RFC850:
|
||||
dt = dt.to_gmt_offset(0); // For named representations of the timezone we always go for GMT, which is required by some RFCs
|
||||
return string::format("%s, %02d-%s-%02d %02d:%02d:%02d GMT", dt.weekday.name, dt.day, dt.month.abbrev, dt.year % 100, dt.hour, dt.min, dt.sec, allocator: allocator);
|
||||
case RFC1123:
|
||||
dt = dt.to_gmt_offset(0); // For named representations of the timezone we always go for GMT, which is required by some RFCs
|
||||
return string::format("%s, %02d %s %d %02d:%02d:%02d GMT", dt.weekday.abbrev, dt.day, dt.month.abbrev, dt.year, dt.hour, dt.min, dt.sec, allocator: allocator);
|
||||
case RFC1123Z:
|
||||
return string::format("%s, %02d %s %d %02d:%02d:%02d %s", dt.weekday.abbrev, dt.day, dt.month.abbrev, dt.year, dt.hour, dt.min, dt.sec, temp_numeric_tzsuffix(dt.gmt_offset), allocator: allocator);
|
||||
case RFC3339:
|
||||
dt = dt.to_gmt_offset(0);
|
||||
return string::format("%04d-%02d-%02dT%02d:%02d:%02dZ", dt.year, dt.month + 1, dt.day, dt.hour, dt.min, dt.sec, allocator: allocator);
|
||||
case RFC3339Z:
|
||||
return string::format("%04d-%02d-%02dT%02d:%02d:%02d%s", dt.year, dt.month + 1, dt.day, dt.hour, dt.min, dt.sec, temp_numeric_tzsuffix_colon(dt.gmt_offset), allocator: allocator);
|
||||
case RFC3339MS:
|
||||
dt = dt.to_gmt_offset(0);
|
||||
return string::format("%04d-%02d-%02dT%02d:%02d:%02d.%dZ", dt.year, dt.month + 1, dt.day, dt.hour, dt.min, dt.sec, dt.usec, allocator: allocator);
|
||||
case RFC3339ZMS:
|
||||
return string::format("%04d-%02d-%02dT%02d:%02d:%02d.%d%s", dt.year, dt.month + 1, dt.day, dt.hour, dt.min, dt.sec, dt.usec, temp_numeric_tzsuffix_colon(dt.gmt_offset), allocator: allocator);
|
||||
case DATETIME:
|
||||
return string::format("%04d-%02d-%02d %02d:%02d:%02d", dt.year, dt.month + 1, dt.day, dt.hour, dt.min, dt.sec, allocator: allocator);
|
||||
case DATEONLY:
|
||||
return string::format("%04d-%02d-%02d", dt.year, dt.month + 1, dt.day, allocator: allocator);
|
||||
case TIMEONLY:
|
||||
return string::format("%02d:%02d:%02d", dt.hour, dt.min, dt.sec, allocator: allocator);
|
||||
}
|
||||
}
|
||||
|
||||
fn String new_format(DateTimeFormat dt_format, TzDateTime dt) => format(dt_format, dt, allocator::heap());
|
||||
fn String temp_format(DateTimeFormat dt_format, TzDateTime dt) => format(dt_format, dt, allocator::temp());
|
||||
|
||||
fn String TzDateTime.format(self, DateTimeFormat dt_format, Allocator allocator) => format(dt_format, self, allocator);
|
||||
fn String TzDateTime.new_format(self, DateTimeFormat dt_format) => format(dt_format, self, allocator::heap());
|
||||
fn String TzDateTime.temp_format(self, DateTimeFormat dt_format) => format(dt_format, self, allocator::temp());
|
||||
|
||||
// .with_gmt_offset(0) instead of .to_local() is used to avoid surprises when user is formatting to a representation without a timezone
|
||||
fn String DateTime.format(self, DateTimeFormat dt_format, Allocator allocator) => format(dt_format, self.with_gmt_offset(0), allocator);
|
||||
fn String DateTime.new_format(self, DateTimeFormat dt_format) => format(dt_format, self.with_gmt_offset(0), allocator::heap());
|
||||
fn String DateTime.temp_format(self, DateTimeFormat dt_format) => format(dt_format, self.with_gmt_offset(0), allocator::temp());
|
||||
|
||||
<*
|
||||
Returns the timezone offset in the format of "+HHMM" or "-HHMM"
|
||||
@require gmt_offset >= -12 * 3600 && gmt_offset <= 14 * 3600
|
||||
*>
|
||||
fn String temp_numeric_tzsuffix(int gmt_offset) @private @inline
|
||||
{
|
||||
if (gmt_offset == 0) return "-0000";
|
||||
return string::tformat("%+03d%02d", gmt_offset / 3600, (gmt_offset % 3600) / 60);
|
||||
}
|
||||
|
||||
<*
|
||||
Returns the timezone offset in the format of "+HH:MM" or "-HH:MM"
|
||||
@require gmt_offset >= -12 * 3600 && gmt_offset <= 14 * 3600
|
||||
*>
|
||||
fn String temp_numeric_tzsuffix_colon(int gmt_offset) @private @inline
|
||||
{
|
||||
if (gmt_offset == 0) return "-00:00";
|
||||
return string::tformat("%+03d:%02d", gmt_offset / 3600, (gmt_offset % 3600) / 60);
|
||||
}
|
||||
@@ -46,34 +46,33 @@ struct TzDateTime
|
||||
int gmt_offset;
|
||||
}
|
||||
|
||||
enum Weekday : char
|
||||
enum Weekday : char (String name, String abbrev)
|
||||
{
|
||||
MONDAY,
|
||||
TUESDAY,
|
||||
WEDNESDAY,
|
||||
THURSDAY,
|
||||
FRIDAY,
|
||||
SATURDAY,
|
||||
SUNDAY,
|
||||
MONDAY = { "Monday", "Mon" },
|
||||
TUESDAY = { "Tuesday", "Tue" },
|
||||
WEDNESDAY = { "Wednesday", "Wed" },
|
||||
THURSDAY = { "Thursday", "Thu" },
|
||||
FRIDAY = { "Friday", "Fri" },
|
||||
SATURDAY = { "Saturday", "Sat" },
|
||||
SUNDAY = { "Sunday", "Sun" },
|
||||
}
|
||||
|
||||
enum Month : char
|
||||
enum Month : char (String name, String abbrev, int days, bool leap)
|
||||
{
|
||||
JANUARY,
|
||||
FEBRUARY,
|
||||
MARCH,
|
||||
APRIL,
|
||||
MAY,
|
||||
JUNE,
|
||||
JULY,
|
||||
AUGUST,
|
||||
SEPTEMBER,
|
||||
OCTOBER,
|
||||
NOVEMBER,
|
||||
DECEMBER
|
||||
JANUARY = { "January", "Jan", 31, false },
|
||||
FEBRUARY = { "February", "Feb", 28, true },
|
||||
MARCH = { "March", "Mar", 31, false },
|
||||
APRIL = { "April", "Apr", 30, false },
|
||||
MAY = { "May", "May", 31, false },
|
||||
JUNE = { "June", "Jun", 30, false },
|
||||
JULY = { "July", "Jul", 31, false },
|
||||
AUGUST = { "August", "Aug", 31, false },
|
||||
SEPTEMBER = { "September", "Sep", 30, false },
|
||||
OCTOBER = { "October", "Oct", 31, false },
|
||||
NOVEMBER = { "November", "Nov", 30, false },
|
||||
DECEMBER = { "December", "Dec", 31, false }
|
||||
}
|
||||
|
||||
|
||||
fn Time now()
|
||||
{
|
||||
$if $defined(native_timestamp):
|
||||
|
||||
49
test/unit/stdlib/time/format.c3
Normal file
49
test/unit/stdlib/time/format.c3
Normal file
@@ -0,0 +1,49 @@
|
||||
module timeformat_test @test;
|
||||
|
||||
import std::time::datetime, std::collections::list, std::collections::triple;
|
||||
|
||||
def FormatTzTestSpec = Triple(<TzDateTime, DateTimeFormat, String>);
|
||||
def FormatTestSpec = Triple(<DateTime, DateTimeFormat, String>);
|
||||
|
||||
fn void test_with_tz()
|
||||
{
|
||||
FormatTzTestSpec[*] tests = {
|
||||
{ datetime::from_date(1970, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(0), RFC1123, "Thu, 01 Jan 1970 00:00:00 GMT" },
|
||||
{ datetime::from_date(1994, Month.from_ordinal(10), 6, 8, 49, 37).with_gmt_offset(0), RFC1123, "Sun, 06 Nov 1994 08:49:37 GMT" },
|
||||
{ datetime::from_date(2020, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(0), RFC1123, "Wed, 01 Jan 2020 00:00:00 GMT" },
|
||||
{ datetime::from_date(2020, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(-3600), RFC1123, "Wed, 01 Jan 2020 01:00:00 GMT" },
|
||||
{ datetime::from_date(2020, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(-3600), RFC1123Z, "Wed, 01 Jan 2020 00:00:00 -0100" },
|
||||
{ datetime::from_date(2020, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(0), RFC1123Z, "Wed, 01 Jan 2020 00:00:00 -0000" },
|
||||
{ datetime::from_date(2020, Month.JANUARY, 1, 0, 0, 0).with_gmt_offset(0), RFC3339, "2020-01-01T00:00:00Z" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 5, 999998).with_gmt_offset(0), RFC3339MS, "2006-01-02T15:04:05.999998Z" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 5, 999998).with_gmt_offset(25200), RFC3339Z, "2006-01-02T15:04:05+07:00" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 5, 999998).with_gmt_offset(25200), RFC3339ZMS, "2006-01-02T15:04:05.999998+07:00" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05).with_gmt_offset(0), RFC822, "02 Jan 06 15:04 GMT" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05).with_gmt_offset(0), RFC822Z, "02 Jan 06 15:04 -0000" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05).with_gmt_offset(0), RFC850, "Monday, 02-Jan-06 15:04:05 GMT" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05).with_gmt_offset(0), UNIXDATE, "Mon Jan 2 15:04:05 GMT 2006" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05).to_gmt_offset(0), RUBYDATE, "Mon Jan 2 15:04:05 -0000 2006" },
|
||||
};
|
||||
|
||||
foreach (test : tests)
|
||||
{
|
||||
String candidate = test.first.new_format(test.second);
|
||||
assert(candidate == test.third, "got: '%s', expected: '%s'", candidate, test.third);
|
||||
}
|
||||
}
|
||||
|
||||
fn void test_without_tz()
|
||||
{
|
||||
FormatTestSpec[*] tests = {
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05), ANSIC, "Mon Jan 2 15:04:05 2006" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05), DATETIME, "2006-01-02 15:04:05" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05), DATEONLY, "2006-01-02" },
|
||||
{ datetime::from_date(2006, Month.JANUARY, 2, 15, 4, 05), TIMEONLY, "15:04:05" },
|
||||
};
|
||||
|
||||
foreach (test : tests)
|
||||
{
|
||||
String candidate = test.first.new_format(test.second);
|
||||
assert(candidate == test.third, "got: '%s', expected: '%s'", candidate, test.third);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user