hare

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

virtual.ha (5437B)


      1 // License: MPL-2.0
      2 // (c) 2021-2022 Byron Torres <b@torresjrjr.com>
      3 use time;
      4 use time::chrono;
      5 
      6 // A [[virtual]] has insufficient information and cannot create a valid datetime.
      7 export type insufficient = !void;
      8 
      9 // A virtual datetime; a [[datetime]] wrapper interface, which represents a
     10 // datetime of uncertain validity. Its fields need not be valid observed
     11 // chronological values. It is meant as an intermediary container for datetime
     12 // information to be resolved with the [[realize]] function.
     13 //
     14 // Unlike [[datetime]], a virtual's fields are meant to be treated as public and
     15 // mutable. The embedded [[time::instant]] and [[time::chrono::locality]] fields
     16 // (.sec .nsec .loc) are considered meaningless. Behaviour with the "observe"
     17 // functions is undefined.
     18 //
     19 // This can be used to construct a new [[datetime]] piece-by-piece. Start with
     20 // [[newvirtual]], then collect enough date/time information incrementally by
     21 // direct field assignments and/or with [[parse]]. Finish with [[realize]].
     22 //
     23 // 	let v = datetime::newvirtual();
     24 // 	v.vloc = time::chrono::UTC;
     25 // 	v.zoff = 0;
     26 // 	datetime::parse(&v, "Date: %Y-%m-%d", "Date: 2038-01-19")!;
     27 // 	v.hour = 03;
     28 // 	v.minute = 14;
     29 // 	v.second = 07;
     30 // 	v.nanosecond = 0;
     31 // 	let dt = datetime::realize(v)!;
     32 //
     33 export type virtual = struct {
     34 	datetime,
     35 	// virtual's locality
     36 	vloc:     (void | time::chrono::locality),
     37 	// locality name
     38 	locname:  (void | str),
     39 	// zone offset
     40 	zoff:     (void | time::duration),
     41 	// zone abbreviation
     42 	zabbr:    (void | str),
     43 	// hour of 12 hour clock
     44 	halfhour: (void | int),
     45 	// AM/PM (false/true)
     46 	ampm:     (void | bool),
     47 };
     48 
     49 // Creates a new [[virtual]]. All its fields are voided or nulled appropriately.
     50 export fn newvirtual() virtual = virtual {
     51 	sec         = 0,
     52 	nsec        = 0,
     53 	loc         = chrono::UTC,
     54 	zone        = null,
     55 	date        = void,
     56 	time        = void,
     57 
     58 	era         = void,
     59 	year        = void,
     60 	month       = void,
     61 	day         = void,
     62 	yearday     = void,
     63 	isoweekyear = void,
     64 	isoweek     = void,
     65 	week        = void,
     66 	sundayweek  = void,
     67 	weekday     = void,
     68 
     69 	hour        = void,
     70 	minute      = void,
     71 	second      = void,
     72 	nanosecond  = void,
     73 
     74 	vloc        = void,
     75 	locname     = void,
     76 	zoff        = void,
     77 	zabbr       = void,
     78 	halfhour    = void,
     79 	ampm        = void,
     80 };
     81 
     82 // Realizes a valid [[datetime]] from a [[virtual]], or fails appropriately.
     83 // Four values require determination. Each has various determination strategies,
     84 // each of which use a certain set of non-void fields from the given virtual.
     85 // The following determination strategies will be attempted in order.
     86 //
     87 // Field sets for determining the date:
     88 //
     89 // 1. date
     90 // 2. year, month, day
     91 // 3. year, yearday
     92 // 4. year, week, weekday
     93 // 5. isoweekyear, isoweek, weekday
     94 //
     95 // Field sets for determining the time:
     96 //
     97 // 1. time
     98 // 2. hour, minute, second, nanosecond
     99 //
    100 // Field sets for determining the zone offset:
    101 //
    102 // 1. zoff
    103 //
    104 // Field sets for determining the [[time::chrono::locality]]:
    105 //
    106 // 1. vloc
    107 // 2. locname
    108 //         This is compared to each provided locality's 'name' field,
    109 //         or "UTC" if none are provided. The first match is used.
    110 // 3. (none)
    111 //         Defaults to [[time::chrono::UTC]].
    112 //
    113 // If for any of these values no determination strategy could be attempted,
    114 // [[insufficient]] is returned. If the resultant datetime is invalid,
    115 // [[invalid]] is returned.
    116 export fn realize(
    117 	v: virtual,
    118 	locs: time::chrono::locality...
    119 ) (datetime | insufficient | invalid) = {
    120 	// determine .date
    121 	if (v.date is i64) {
    122 		void;
    123 	} else if (
    124 		v.year is int &&
    125 		v.month is int &&
    126 		v.day is int
    127 	) {
    128 		v.date = calc_date__ymd(
    129 			v.year as int,
    130 			v.month as int,
    131 			v.day as int,
    132 		)?;
    133 	} else if (
    134 		v.year is int &&
    135 		v.yearday is int
    136 	) {
    137 		v.date = calc_date__yd(
    138 			v.year as int,
    139 			v.yearday as int,
    140 		)?;
    141 	} else if (
    142 		v.year is int &&
    143 		v.week is int &&
    144 		v.weekday is int
    145 	) {
    146 		v.date = calc_date__ywd(
    147 			v.year as int,
    148 			v.week as int,
    149 			v.weekday as int,
    150 		)?;
    151 	} else if (false) {
    152 		// TODO: calendar.ha: calc_date__isoywd()
    153 		void;
    154 	} else {
    155 		// cannot deduce date
    156 		return insufficient;
    157 	};
    158 
    159 	// determine .time
    160 	if (v.time is time::duration) {
    161 		void;
    162 	} else {
    163 		const hour = if (v.hour is int) {
    164 			yield v.hour as int;
    165 		} else if (v.halfhour is int && v.ampm is bool) {
    166 			const hr = v.halfhour as int;
    167 			const pm = v.ampm as bool;
    168 			yield if (pm) hr * 2 else hr;
    169 		} else {
    170 			return insufficient;
    171 		};
    172 
    173 		if (
    174 			v.minute is int &&
    175 			v.second is int &&
    176 			v.nanosecond is int
    177 		) {
    178 			v.time = calc_time__hmsn(
    179 				hour,
    180 				v.minute as int,
    181 				v.second as int,
    182 				v.nanosecond as int,
    183 			)?;
    184 		} else {
    185 			return insufficient;
    186 		};
    187 	};
    188 
    189 	// determine zone offset
    190 	if (v.zoff is time::duration) {
    191 		void;
    192 	} else {
    193 		return insufficient;
    194 	};
    195 
    196 	// determine .loc (defaults to time::chrono::UTC)
    197 	if (v.vloc is chrono::locality) {
    198 		v.loc = v.vloc as chrono::locality;
    199 	} else if (v.locname is str) {
    200 		v.loc = chrono::UTC;
    201 		for (let i = 0z; i < len(locs); i += 1) {
    202 			const loc = locs[i];
    203 			if (loc.name == v.locname as str) {
    204 				v.loc = loc;
    205 				break;
    206 			};
    207 		};
    208 	};
    209 
    210 	// determine .sec, .nsec
    211 	const dt = from_moment(chrono::from_datetime(
    212 		v.loc,
    213 		v.zoff as time::duration,
    214 		v.date as i64,
    215 		v.time as time::duration,
    216 	));
    217 
    218 	// verify zone offset
    219 	const z = chrono::mzone(&dt);
    220 	if (z.zoff != v.zoff as time::duration) {
    221 		return invalid;
    222 	};
    223 
    224 	return dt;
    225 };