torresjrjr(7) 0xF7959E95

Martian time in Hare

· Byron Torres

This article has been republished at the Hare blog.

Preface

Development of the Hare programming language is chugging along. From what I’ve gathered from IRC, Drew DeVault has been spending most of his hare-dev time on Helios, in an effort to throw real code at Hare and scrutinize its design choices. For example, since then, a @threadlocal keyword has come into consideration.

My focus area is naturally the datetime and time::chrono modules of the standard library, both of which I primarily wrote, and which I maintain and intend to for a long time. These modules too have been made with unique design choices.

To test the robustness and modularity of datetime and time::chrono, I’ve been working on hare-mbc, a Hare library which provides a Martian chronology based upon the Martian Business Calendar by Bruce Mills. I’ve talked about chronologies, datetime and time::chrono in some depth already at the Hare blog, so I’ll try not to repeat much here.

Chronology in Hare
https://harelang.org/blog/2022-04-17-chronology-in-hare/

I decided to implement the Martian Business Calendar over other more commonly known ones like the Darian, Utopian or Martiana calendars, because I liked its use of leap weeks better than leap days, which does not interrupt the week cycle and keeps the weekdays in the same position in every month and year.

In the MBC chronology, Mars years have either 665 or 672 sols (Martian days), about twice that of Earth’s. Years have 24 months, each having 28 sols, except for the last month of the year, which skips an intercalary week nearly every second year.

I found this design to be easier to implement than datetime’s Gregorian calendar. The MBC calendar has a cycle of 76 years (50813 sols). Unlike datetime which uses a mathematical formula, I had hare-mbc use a lookup table to calculate the number of sols up to a given year in a cycle.

Demo

Let’s explore Martian timekeeping and chronology in Hare with hare-mbc.

NOTE: this library is incomplete and untested. The following code uses a forked version of the standard library, which I suspect to be merged soon after review. Nonetheless, there are some interesting results here.

hare-mbc comes with the time::mbc module. Using our trusty haredoc tool, we can get its documentation.

$ haredoc -Fhtml time::mbc

This module is based off the datetime module, though I left out a lot of stuff like parsing, datetime arithmetic and pseudo-datetime functionality, to keep the module small for the time being. Let’s import time::mbc and some other stdlib modules to play with.

use time;
use time::mbc;
use time::chrono;
use datetime;
use fmt;

Let’s create a few MBC datetimes.

// MBC epoch
let epoc = mbc::new(chrono::MTC, 0)!;

// Unix epoch
let unix = mbc::new(chrono::MTC, 0, 0191,20,23, 07,06,01,057366670)!;

// My birthday
let birt = mbc::new(chrono::MTC, 0, 0207,11,16, 08,49,27,279563480)!;

// Hare's first commit.
let hare = mbc::new(chrono::MTC, 0, 0218,10,19, 09,20,53,344357297)!;

Notice we’re using the stdlib’s chrono::MTC locality (Coordinated Mars Time), and thus using the chrono::mtc timescale by proxy. We are also specifying an offset of 0 for all our datetimes. We won’t be experimenting with offsetted timezones today, though they should work just fine. Perhaps I’ll add Airy-0 and other such Martian timezones.

The declaration for epoc uses mbc::new’s short form, which is equivalent to this:

let epoc = mbc::new(chrono::MTC, 0, 0000,01,01, 00,00,00,000000000)!;

The same moment on Earth occurs on the Gregorian date 1609 March 12th at 19:19:16 UTC. This is the Telescopic epoch, named after the fact that Galileo Galilei first observed Mars around this time. The Utopian calendar website details the calculation of this date, which the stdlib’s chrono::mtc timescale already does in a similar fashion.

Let’s format and print these datetimes.

const buf: [64]u8 = [0...];

fmt::println(mbc::bsformat(buf, mbc::STAMP_NOZL, &epoc)!)!;
fmt::println(mbc::bsformat(buf, mbc::STAMP_NOZL, &unix)!)!;
fmt::println(mbc::bsformat(buf, mbc::STAMP_NOZL, &birt)!)!;
fmt::println(mbc::bsformat(buf, mbc::STAMP_NOZL, &hare)!)!;
fmt::println()!;
fmt::println(mbc::bsformat(buf, mbc::STELLAR, &epoc)!)!;
fmt::println(mbc::bsformat(buf, mbc::STELLAR, &unix)!)!;
fmt::println(mbc::bsformat(buf, mbc::STELLAR, &birt)!)!;
fmt::println(mbc::bsformat(buf, mbc::STELLAR, &hare)!)!;
fmt::println()!;
fmt::println(mbc::bsformat(buf, mbc::EMAIL_Z, &epoc)!)!;
fmt::println(mbc::bsformat(buf, mbc::EMAIL_Z, &unix)!)!;
fmt::println(mbc::bsformat(buf, mbc::EMAIL_Z, &birt)!)!;
fmt::println(mbc::bsformat(buf, mbc::EMAIL_Z, &hare)!)!;
fmt::println()!;

Output:

0000-01-01 00:00:00.000000000 +0000 MTC MTC
0191-20-23 07:06:01.057366670 +0000 MTC MTC
0207-11-16 08:49:27.279563480 +0000 MTC MTC
0218-10-19 09:20:53.344357297 +0000 MTC MTC

0000 Sagittarius 01, Mon 00:00 MTC
0191 Boötes 23, Tue 07:06 MTC
0207 Taurus 16, Tue 08:49 MTC
0218 Perseus 19, Fri 09:20 MTC

Mon, 01 Sgtr 0000 00:00:00 +0000 MTC
Tue, 23 Boot 0191 07:06:01 +0000 MTC
Tue, 16 Taur 0207 08:49:27 +0000 MTC
Fri, 19 Pers 0218 09:20:53 +0000 MTC

MBC optionally includes a Latin-based naming scheme for the 24 months using constellations, 12 of which are from the zodiac. hare-mbc provides these names, as well as IAU’s 3-letter and NASA’s 4-letter constellation abbreviations.

To simplify things, let’s write a pretty printing helper function.

fn show(ls: (datetime::datetime | mbc::datetime)...) void = {
	for (let i = 0z; i < len(ls); i += 1) {
		match (ls[i]) {
		case let dt: datetime::datetime =>
			fmt::println(datetime::bsformat(buf,
				datetime::EMAILZ, &dt)!)!;
		case let dt: mbc::datetime =>
			fmt::println(mbc::bsformat(buf,
				mbc::EMAIL_Z, &dt)!)!;
		};
	};
	fmt::println()!;
};

Let’s convert our Martian datetime birt to an Earthly datetime and back. This is actually my birthday. Coincidentally, as you can see, I’m a Taurus in two chronologies.

let earth = datetime::from_moment(
	chrono::in(chrono::UTC, *(&birt: *chrono::moment))
);
let mars = mbc::from_moment(
	chrono::in(chrono::MTC, *(&earth: *chrono::moment))
);
show(birt, earth, mars);

Output:

Tue, 16 Taur 0207 08:49:27 +0000 MTC
Thu, 13 May 1999 00:00:00 +0000 UTC
Tue, 16 Taur 0207 08:49:27 +0000 MTC

Here we are making a pointer to birt, casting it to a chrono::moment type, and dereferencing our new moment, which chrono::in accepts. We’re using a special property of mbc::datetime – the fact that it embeds chrono::moment, just like the stdlib datetime::datetime type. Third-party libraries like hare-mbc are expected to create their own datetime types like this, thus unifying all datetime types.

The chrono::moment type in turn embeds time::instant. That is to say, every datetime is really a time::instant with extra information, notably a .loc field for the moment’s chrono::locality, itself containing a .timescale field for the locality’s chrono::timescale.

We ask chrono::in to change the locality of our Martian datetime from MTC to UTC. Since these two localities use different timescales, a conversion from one timescale to the other will occur, like the following code. Hare uses chrono::tai as an intermediary timescale for conversions.

let mtc = *(&mars: *time::instant);
let utc = chrono::utc.from_tai(chrono::mtc.to_tai(mtc)!)!;
let earth = datetime::from_instant(chrono::UTC, utc);
show(earth);

Output:

Thu, 13 May 1999 00:00:00 +0000 UTC

MBC uses the common 24-hour clock to track time throughout a sol, just like we do with days on earth. That’s 86400 seconds per day/sol. However, Martian-time runs ~3% slower than Earth-time, and we account for this by linearly scaling Martian seconds when converting.

This also means adding a time::duration to a datetime requires knowledge of what timescale you’re working with. For example:

let nextday_utc = datetime::from_instant(
	chrono::UTC,
	time::add(*(&earth: *time::instant), 24 * time::HOUR),
);

let mars_nextday = mbc::from_instant(
	chrono::MTC,
	time::add(*(&mars: *time::instant), 24 * time::HOUR),
);
let nextday_mtc = datetime::from_moment(
	chrono::in(chrono::UTC, *(&mars_nextday: *chrono::moment)),
);

show(nextday_utc, nextday_mtc);

Output:

Fri, 14 May 1999 00:00:00 +0000 UTC
Fri, 14 May 1999 00:39:35 +0000 UTC

Indeed, 24 hours in Martian-time is equal to 24 hours, 39 minutes, and 35 seconds in Earth-time.

Conclusion

The library does have some warts, and the floating point arithmetic used internally can sometimes cause slight inaccuracies with the nanosecond (should be fixable). Nonetheless, I think Hare has proven quite capable for handling foreign chronologies. We didn’t just handle another calendar system, but another underlying timescale too, and quite seamlessly. This enabled us to model our world very accurately.

hare-mbc is to be refined and expanded, and be a blueprint for future calendar libraries. We will need implementations for common chronologies such as the Japanese, Hijri, Hebrew, and Thai-Buddhist calendars, etc. I also have hare-mayan awaiting in the lab, which uses a 5-unit modified vigesimal calendar.

This experimental demo was fun, and I hope Hare can continue to provide good timekeeping capabilities up to Hare 1.0 and beyond. Maybe even in space, who knows?