hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 374b7596a0bd3b61477e1b98cf2c5163a7f364a2
parent 3c1b9cf1a7851a2d70a622cd1bcf5cf638fa3e8f
Author: Byron Torres <b@torresjrjr.com>
Date:   Mon, 15 Nov 2021 22:11:07 +0000

adapt fmttime() code

The fmttime() function is outlined and includes code adapted from
Vlad-Stefan Harbuz <vlad@vladh.net>.

The localdate{} and localtime{} structs now have an epochal and lapsed
field respectively, to extend chrono::moment. Choices as the the
underlying types are to be made.

Signed-off-by: Byron Torres <b@torresjrjr.com>

Diffstat:
Mdatetime/calendar.ha | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mdatetime/date.ha | 27++++++++-------------------
Mdatetime/datetime.ha | 3+++
Mdatetime/format.ha | 183++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mdatetime/time.ha | 19+++++++++++--------
5 files changed, 257 insertions(+), 51 deletions(-)

diff --git a/datetime/calendar.ha b/datetime/calendar.ha @@ -46,56 +46,68 @@ export fn conv_moment_datetime(m: chrono::moment, dt: *datetime) void = { // // Calculates a moment's number of days since the calendar epoch 0001-01-01 -export fn epocal(m: chrono::moment) int = { - return (EPOCH_COMMONERA + m.date): int; +export fn epochal(dt: *datetime) int = { + match (dt.date.epochal) { + case void => + abort("TODO"); + case e: int => + return e; + }; }; // Calculates a moment's era -export fn era(m: chrono::moment) int = { - if (m.date >= EPOCH_COMMONERA) { - return 1; // CE, Common Era +export fn era(dt: *datetime) int = { + if (epochal(dt) >= EPOCH_COMMONERA) { + dt.date.era = 1; // CE, Common Era } else { - return 0; // BCE, Before Common Era + dt.date.era = 0; // BCE, Before Common Era + }; + const a = match (dt.date.era) { + case void => + abort("TODO"); + case a: int => + yield a; }; + return a; }; // Calculates a moment's year -export fn year(m: chrono::moment) int = { +export fn year(dt: *datetime) int = { return 0; // TODO }; // Calculates a moment's month -export fn month(m: chrono::moment) int = { +export fn month(dt: *datetime) int = { return 0; // TODO }; // Calculates a moment's day of the month -export fn day(m: chrono::moment) int = { +export fn day(dt: *datetime) int = { return 0; // TODO }; // Calculates a moment's ISO week calendar year -export fn isoweekyear(m: chrono::moment) int = { +export fn isoweekyear(dt: *datetime) int = { return 0; // TODO }; // Calculates a moment's ISO week -export fn isoweek(m: chrono::moment) int = { +export fn isoweek(dt: *datetime) int = { return 0; // TODO }; // Calculates a moment's Gregorian week -export fn week(m: chrono::moment) int = { +export fn week(dt: *datetime) int = { return 0; // TODO }; // Calculates a moment's day of the week -export fn weekday(m: chrono::moment) int = { +export fn weekday(dt: *datetime) int = { return 0; // TODO }; // Calculates a moment's ordinal day of the year -export fn yearday(m: chrono::moment) int = { +export fn yearday(dt: *datetime) int = { return 0; // TODO }; @@ -104,23 +116,43 @@ export fn yearday(m: chrono::moment) int = { // // Calculates a moment's hour of the day -export fn hour(m: chrono::moment) int = { - return (m.time / time::HOUR): int; +export fn hour(dt: *datetime) int = { + match (dt.time.hour) { + case void => + abort("TODO"); + case h: int => + return h; + }; }; // Calculates a moment's minute of the hour -export fn min(m: chrono::moment) int = { - return ((m.time / time::MINUTE) % 60): int; +export fn min(dt: *datetime) int = { + match (dt.time.min) { + case void => + abort("TODO"); + case m: int => + return m; + }; }; // Calculates a moment's second of the minute -export fn sec(m: chrono::moment) int = { - return ((m.time / time::SECOND) % 60): int; +export fn sec(dt: *datetime) int = { + match (dt.time.sec) { + case void => + abort("TODO"); + case s: int => + return s; + }; }; // Calculates a moment's nanosecond of the second -export fn nsec(m: chrono::moment) int = { - return (m.time % time::SECOND): int; +export fn nsec(dt: *datetime) int = { + match (dt.time.nsec) { + case void => + abort("TODO"); + case n: int => + return n; + }; }; diff --git a/datetime/date.ha b/datetime/date.ha @@ -4,6 +4,7 @@ use errors; // Represents an ISO calendar date. // Instances created from datetime:: functions are guaranteed to be valid. export type localdate = struct { + epochal: (void | int), era: (void | int), year: (void | int), month: (void | int), @@ -16,6 +17,7 @@ export type localdate = struct { }; export fn init_date() localdate = localdate { + epochal = void, era = void, year = void, month = void, @@ -27,17 +29,6 @@ export fn init_date() localdate = localdate { weekday = void, }; -export type WEEKDAYS = enum int { - MON = 1, - TUE = 2, - WED = 3, - THU = 4, - FRI = 5, - SAT = 6, - SUN = 7, -}; - - // Calculates an era, given a year fn calc_era(y: int) int = { return if (y >= 0) { @@ -48,10 +39,8 @@ fn calc_era(y: int) int = { }; -// Calculates the (year, month, day) of a [[chrono::epochal]] -// -// TODO: Split up this function? Probably not. Most useful as is. -fn calc_ymd(e: chrono::epochal) (int, int, int) = { +// Calculates the (year, month, day), given an epochal day +fn calc_ymd(e: int) (int, int, int) = { // Algorithm adapted from: // https://en.wikipedia.org/wiki/Julian_day#Julian_or_Gregorian_calendar_from_Julian_day_number // @@ -167,14 +156,14 @@ fn calc_week(yd: int, wd: int) int = { return (5 + yd - wd) / 7; }; -// Calculates the weekday of a [[chrono::epochal]], +// Calculates the weekday, given a epochal day // from Monday=1 to Sunday=7 fn calc_weekday(e: chrono::epochal) int = { const wd = ((e + 3) % 7 + 1): int; return if (wd > 0) wd else wd + 7; }; -// Calculates the weekday of a [[chrono::epochal]], +// Calculates the weekday, given a epochal day // from Monday=0 to Sunday=6 fn calc_zeroweekday(wd: int) int = { return wd - 1; @@ -202,7 +191,7 @@ export fn conv_epochal_localdate( want: *localdate, ) void = { if (want.year is int || want.month is int || want.day is int) { - const ymd = calc_ymd(e); + const ymd = calc_ymd(e: int); date.year = ymd.0; date.month = ymd.1; date.day = ymd.2; @@ -210,7 +199,7 @@ export fn conv_epochal_localdate( if (want.yearday is int) { if (date.year is void || date.month is void || date.day is void) { - const ymd = calc_ymd(e); + const ymd = calc_ymd(e: int); date.year = ymd.0; date.month = ymd.1; date.day = ymd.2; diff --git a/datetime/datetime.ha b/datetime/datetime.ha @@ -67,6 +67,7 @@ export fn new_datetime( ) (datetime | errors::invalid) = { const dt = datetime { date = localdate { + epochal = void, era = if (year >= EPOCH_COMMONERA) 1 else 0, year = year, month = month, @@ -79,6 +80,7 @@ export fn new_datetime( }, time = localtime { + lapsed = void, hour = hour, min = min, sec = sec, @@ -124,6 +126,7 @@ export fn now_datetime() datetime = { const dt = datetime { date = ld, time = localtime { + lapsed = ((i.sec / 86400) * time::NANOSECOND + i.nsec): int, hour = (i.sec / 3600): int % 24, min = (i.sec / 60): int % 60, sec = i.sec: int % 60, diff --git a/datetime/format.ha b/datetime/format.ha @@ -1,9 +1,44 @@ use errors; use fmt; use io; +use strconv; use strings; use strio; +def WEEKDAYS: [_]str = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", +]; + +def WEEKDAYS_SHORT: [_]str = ["Mon", "Tue", "Wed", "Thu", "Fr", "Sat", "Sun"]; + +def MONTHS: [_]str = [ + "January", + "Feburary", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; + +def MONTHS_SHORT: [_]str = [ + "Jan", "Feb", "Mar", + "Apr", "May", "Jun", + "Jul", "Aug", "Sep", + "Oct", "Nov", "Dec", +]; + // Parses a string into a [[datetime]] export fn strptime(format: str, s: str, dt: *datetime) (void | errors::invalid) = { // TODO @@ -32,6 +67,150 @@ export fn strftime(format: str, dt: *datetime) (str | errors::invalid | io::erro // Formats a [[datetime]] and writes it into a [[io::handle]]. // Fails a particular field is required but void. export fn fmttime(h: io::handle, format: str, dt: *datetime) (size | errors::invalid | io::error) = { - // TODO - return 0z; + let iter = strings::iter(format); + let escaped = false; + let n = 0z; + for (true) { + let r: rune = match (strings::next(&iter)) { + case void => + break; + case r: rune => + yield r; + }; + + if (!escaped && r == '%') { + escaped = true; + continue; + }; + + if (!escaped) { + strio::appendrune(h, r)?; + continue; + }; + + escaped = false; + let new = switch (r) { + case 'a' => + yield WEEKDAYS_SHORT[weekday(dt) - 1]; + case 'A' => + yield WEEKDAYS[weekday(dt) - 1]; + case 'b' => + yield MONTHS_SHORT[month(dt) - 1]; + case 'h' => + yield strftime("%b", dt)?; + case 'B' => + yield MONTHS[month(dt) - 1]; + case 'c' => + // TODO: Localization + yield strftime("%a %b %e %H:%M:%S %Y", dt)?; + case 'C' => + yield strconv::itos(year(dt) / 100); + case 'D' => + yield strftime("%m/%d/%y", dt)?; + case 'd' => + yield fmt::asprintf("{:02}", day(dt)); + case 'e' => + yield fmt::asprintf("{:2}", day(dt)); + case 'F' => + yield strftime("%Y-%m-%d", dt)?; + case 'g' => + let year_str = strconv::itos(isoweekyear(dt)); + yield strings::sub(year_str, + len(year_str) - 2, strings::end); + case 'G' => + yield strconv::itos(isoweekyear(dt)); + case 'H' => + yield fmt::asprintf("{:02}", hour(dt)); + case 'I' => + let mod_hour = hour(dt) % 12; + if (mod_hour == 0) { + mod_hour = 12; + }; + yield fmt::asprintf("{:02}", mod_hour); + case 'j' => + yield strconv::itos(yearday(dt)); + case 'k' => + yield strconv::itos(hour(dt)); + case 'l' => + let mod_hour = hour(dt) % 12; + if (mod_hour == 0) { + mod_hour = 12; + }; + yield strconv::itos(mod_hour); + case 'm' => + yield fmt::asprintf("{:02}", month(dt)); + case 'M' => + yield fmt::asprintf("{:02}", min(dt)); + case 'n' => + yield "\n"; + case 'N' => + yield strconv::itos(nsec(dt)); + case 'p' => + // TODO: Localization + yield if (hour(dt) < 12) { + yield "AM"; + } else { + yield "PM"; + }; + case 'P' => + // TODO: Localization + yield if (hour(dt) < 12) { + yield "am"; + } else { + yield "pm"; + }; + case 'r' => + // TODO: Localization + yield strftime("%I:%M:%S %p", dt)?; + case 'R' => + yield strftime("%H:%M", dt)?; + case 'S' => + yield fmt::asprintf("{:02}", sec(dt)); + case 't' => + yield "\t"; + case 'T' => + yield strftime("%H:%M:%S", dt)?; + case 'u' => + yield strconv::itos(weekday(dt)); + case 'U' => + // yield fmt::asprintf("{:02}", week_starting_sunday(dt)); + // TODO + yield ""; + case 'V' => + yield fmt::asprintf("{:02}", isoweek(dt)); + case 'w' => + yield strconv::itos(weekday(dt) % 7); + case 'W' => + yield fmt::asprintf("{:02}", week(dt)); + case 'x' => + // TODO: Localization + yield strftime("%m/%d/%y", dt)?; + case 'X' => + // TODO: Localization + yield strftime("%H:%M:%S", dt)?; + case 'y' => + let year_str = strconv::itos(year(dt)); + yield strings::sub(year_str, + len(year_str) - 2, strings::end); + case 'Y' => + yield strconv::itos(year(dt)); + case 'z' => + //yield get_tz_hhmm(dt); + // TODO + yield ""; + case 'Z' => + //yield get_tz_name(dt); + // TODO + yield ""; + case '%' => + yield "%"; + case => + // Pass-through invalid conversion specifier characters. + strio::appendrune(h, '%')?; + strio::appendrune(h, r)?; + continue; + }; + n += strio::concat(h, new)?; + }; + return n; }; diff --git a/datetime/time.ha b/datetime/time.ha @@ -3,22 +3,25 @@ use time; // Represents a time of day as represented on a wall clock export type localtime = struct { - hour: (void | int), - min: (void | int), - sec: (void | int), - nsec: (void | int), + lapsed: (void | int), + hour: (void | int), + min: (void | int), + sec: (void | int), + nsec: (void | int), }; export fn init_time() localtime = localtime { - hour = void, - min = void, - sec = void, - nsec = void, + lapsed = void, + hour = void, + min = void, + sec = void, + nsec = void, }; // Converts a [[time::duration]] to a [[localtime]] export fn conv_time_localtime(t: time::duration) localtime = { const lt = localtime { + lapsed = t: int, hour = (t / time::HOUR): int, min = ((t / time::MINUTE) % 60): int, sec = ((t / time::SECOND) % 60): int,