again idk if I will be doing sd instead of macondo but anyways, been doing documentation :) I also fixed ammarstime and did bindings to other langs.
again idk if I will be doing sd instead of macondo but anyways, been doing documentation :) I also fixed ammarstime and did bindings to other langs.
Again, in case I switch back from Macondo. More fighting with leap years - I’ll have to use AI for help. In the meantime I created a marsdate program, and did wcsfmarstime (analogous to wcsftime). I also created some initial Rust bindings :)
Devlog now in case I switch from Macondo to Stardance. In the event that I need to, I will edit this with more details. Basically, I managed to work on strfmarstime, strpmarstime, ammarstime, and I’ve been drilling testcases.
Nothing crazy to report for the current testsuite and implementation verification I’ve been doing. Basically same thing as I did before I took a break for strpmarstime.
For some formats, it will literally print anything (if it’s invalid), which appears to be normal in Glibc, so it’s normal to me.
Some locale work too on AM/PM formatting and time formatting - some do “AM”, some do “am”, Spain does “a.m.”.
The big thing is:
Okay, so there are three different definitions of week numbers:
%U: The first day of week 1 is on Sunday. Range 00 (because sometimes in January we don’t get to Sunday for a few days), up until 53 (sometimes we actually get 53 weeks due to day placement.)
%V: ISO 8601, which has week 1 as the first week with at least 4 days in the new year. Range 01 to 53. (Week 0 would be represented by 52, in my testing)
%W: The first day of week 1 is on Monday.
But, here is the key thing about the Darian calendar: Each month resets the week. This is actually one of the things about the Gregorian Calendar some people don’t like-how the # of the day is on inconsistent weekdays.
The Darian Calendar has a neat 28 days for each month, so with 7 days a week, this isn’t a problem. And since each week is guaranteed to have four days, we can just take the number of days and divide by 7. For %W, shift by 1, because Monday (Lunae) is not the first day of the week.
However.. every 6th month is the end of a season, and it has 27 days. So we need to, in our calculations, which must go based off the nth day of the year, make sure we reset, otherwise we will lose our day alignment (because again, the week resets). So we need to realign every 167 days, and also check how many seasons passed and use that to get the amount of prior weeks we are skipping when we have modulated.
As for the leap day: It divides perfectly by 7 and 167, so it gets its own if statement case (otherwise it would read “97”).
I knew this would be weird to figure out, and challenging, but I actually had a lot of fun doing it. It kept my brain working, I stayed energetic, and I managed to have fun, which is the most important thing!
So after my long Pomodoro break, I’ll verify %W (since %U and %V are thee same), and then %w, %x, %X, %y and %Y and that will be it, since for an initial ship I don’t plan to do timezones.
Using musl as a reference, I’ve completed an.. implementation, of strpmarstime. Yesterday I must’ve placed my energy wrong because I just wasn’t super motivated and didn’t do as much coding.
Anyway:
strpmarstime is the inverse of strfmarstime, taking a date-time formatted string and given its format, entering those values into a mars_tm struct.
Everything made sense, mostly. There are still some areas of code I need to comment and reread to make sure I understand what is going on. But after strfmarstime, it isn’t too crazy.
As usual, it does the things for string parsing, like str++ to move to the rest of the string’s characters, then it parses by item.
In order to find values, I think that the calculations sort of estimate the character location of the number range, and then we import it.
Once again, I found myself in a situation where musl can lookup strings to match values like day and month names via numerical item addresses with nl_langinfo, but I cannot (as this is not a “standard” item index). Musl is able to use this item address to iterate through the list of potential matches, which is easy to do in common goto routines.
For me, I had to basically make a pointer to an array of char* (so I had char**, once I figured it out), that was the “iteration_array”. Depending on the format, it would check the array for the match. The main confusing thing was really just the pointers. Spent about 10 minutes fighting with compiler errors until I figured it out with the help of stackoverflow and Google’s AI.
By the way, using nl_langinfo_l, which I did in strfmarstime, does not exist in locale.h by default on macOS, it’s in xlocale.h, so I made a small configure define this morning to take care of that.
There are several ways of determining what # week of year we are in. One format says it’s the week with at least four days, and starts indexing at like, 1 I think? And then there are a few others? I can’t remember off the top of my head.
Because there are no clear answers, I (and musl) have left these formats as “parse only”.
This really impacts week number formats, which, I guess makes sense, because if you only have that, who knows what year you are in, and how many days are in the first full “week”, however you define that.
Skipped for now, as I have yet to take care of this, and I think some proposed timekeeping systems actually don’t do the standard one-hour intervals.. I need to look into it.
Welp, it’s off to testing and verification. Yay!
I took a break from validating strfmarstime because.. well I needed it. So I started doing the function that does the opposite, takes a string, parses into into a mars_tm structure.
I am taking the same approach as musl, and enjoying myself, as in, because musl is able to lookup string matches by locale (because there is a fixed offset for dates), but I can’t, I have to manually search. But instead of copy pasting code I’m trying to setup a pointer to an array to check in a common code routine.
And now I’m getting memory issues. Yay!
The %c format specifier checks the system locale for how to format the date and time.
Of course, it’s a good idea to test this. It took me a while.. unfortunately I don’t have a way of checking in test configuration if one of the formats is installed on the host system (see https://en.wikipedia.org/wiki/List_of_date_formats_by_country). I also don’t really have a nice way (as far as I know) to set test environment variables.
So what I ended on was in the test setup, just passing the LC_ALL environment variable. But this was broken for some reason?
Ah, it turns out that my logic in strfmarstime to get the locale was incorrect. For some reason,
setlocale(LC_ALL, NULL);
does not work, and you have to use “”. My guess is that NULL goes to system default, and “” will read from environment variables.
Yay!
While Day-Month-Year format is straight forward, the few countries that use Year-Month-Day are driving me insane, like Hungary, because their formats have like, dots in them, and I’m not seeing much consistency across the other country’s formats that also have Year-Month-Day formatting.
I’ll come back to it later. It’s actually broken anyway, because my week number representations are still wrong.
After about 6 hrs total, the strfmarstime function is complete, using musl as a reference, except:
This was quite the grind, having strftime docs open on one screen, musl’s implementation on another, and trying to go through each format while documenting and understanding how the formatting is being processed, one by one.
I also did some slight restructuring: the __mars_tm_to_secs function exists. It is basically what mkmarstime did. I would have it not subtract by epoch, but it was causing tests to fail. Technically the strftime docs say that %s, which uses it, calls the function anyways, so it may be pointless but whatever.
Oh, speaking of %s: That is seconds since the Unix epoch. Who knows how useful that will be when this calendar implementation starts at MSD 0 (like, 1873). Also, it’s martian seconds instead of Earth seconds.
Fun shoutout to “%%”, “%t”, and “%n”, which required nothing, just setting the length of the item to “1” and returning the actual formatting character. Lol.
Welp, now it’s time to test and drill this function into oblivion.
I think it is warranted, otherwise I will forget later. This is part 1 of many on the journey to the strfmarstime function - which returns a formatted string of a broken down mars time structure.
This was my first time ever recreating a string formatting function - especially in C, so I learned some things along the way.
After some attempts, it became pretty clear that, although string formatting functions may seem easy, when you need to support width and padding specifiers, things get complicated.
Since I had to learn this, I decided to save time by basically recreating musl’s strftime implementation side by side. I would comment and give details on what is happening so I learned how it worked while also getting the result I need to, without just blatantly copying and pasting without giving credit.
This took about an hour since some of the variable names in musl’s implementation are like, ‘l’, ‘f’, and I had to keep it straight. I actually am writing this devlog after I spent 30+ minutes debugging some stack smashing that was happening, as a result of me putting in the wrong variable for string length into memcpy.
Basically, strftime/strfmarstime will first collect information needed for each format specifier, like if there is a character requesting padding. Then it will do each item at a time, with the given padding and width.
Here’s a fun thing I learned: There is a lot of “format++”. But format is a char*. How does that make sense?
Well, format++ actually is basically the same as doing .substring(1, format.length()) in other languages. I guess it makes sense if you think about a list of characters, and you are progressing to the next character. (Like a “cdr” of a “cons” box in functional programming).
So that helped.
I wanted to come back to this later, especially because I will eventually need translations. But the %c specifier actually refers to the system locale’s date and time format. This required some hacking that came around the time I was stuck with that stack smashing. So seriously, I was stuck.
musl has it easy, it can tell by its own thread what the locale is. Here, we need to initialize locales (setlocale), and then make a locale_t object we can use with newlocale. Then we free it at the end.
Since the locale’s DT formatting is really just a shorthand for other specifiers, it returns “-” in its place. But as I continue forward, it will work.
Probably the hurdle that will be the second hardest part of the entire libspacetime library is ammarstime. It is the same as the C stdlib’s “gmtime”, which, from a timestamp, returns a broken down time structure (struct tm) of the date/time info of that timestamp (the human readable info like day, month, year, etc.)
This is actually difficult. Seconds just count up, and you have to go backwards. You can try to divide by the amount of seconds in a year, but what about leap years?
Honestly, the math is too weird, even for me. Some parts make sense, but the rest, eh…
I referred to musl’s “secs_to_tm” function for how it did the calculations. This is what mostly guided my work, with a few pieces of calculation and numbers help from Google AI search results.
I didn’t write a test suite for this function, because I think it will be easier to do once I knock down what will be the hardest kinds of functions: *string manipulation *(e.g. strftime)
But now that I have a way to convert seconds to a mars_tm struct, it should make everything else somewhat easier. Hopefully.
I added some tests for Y2038, before, and after, because, of course, that could be an issue. It turned out that Darian timestamps on non-leap years (even-numbered years) were incorrect. But they were correct in leap years.
So… what gives?
I thought it could be the result of the calendar epoch being incorrect (it did end up being slightly incorrect) - but I tried using other dates on non-leap years, and couldn’t find a consistent offset that all the dates were off by.
This was.. really weird. I also tried setting the year to calculate the year before - no dice.
Eventually, I decided, “screw it”, so I took GPT-5 mini and asked it to try to figure out what was happening.
Spoiler alert: It did eventually figure everything out.. but it was.. quite the process to watch it do so.
It detected that the sols-passed-at-the-start-of-month table was actually missing the value for the 8th month. Of course, adjusting this caused tests to fail, so I was hesitant to trust it. It also suggested subtracting 1 from the day count when multiplying by 86400 seconds per day. This was bogus.
I asked it to make a .patch or a .diff I could apply - and it didn’t work. Missing headers or invalid patch data, whatever it was, it can’t generate a .patch file. Which is kind of hilarious.
It also didn’t look back at my codebase when I applied some of it’s suggestions, so it would be making patches that refer to deleted lines that didn’t exist anymore. Could just be a skill issue.
I told it to figure out what was wrong. It spent the next ~16 mins, making a “debug mkmarstime” script, and a bunch of other small scripts like “probe_unix”, “probe_y2038” - in some cases, it was just going around in a circle - making changes that only resulted in the same prior status. I thought it was hallucinating at first, especially with the compiler errors it got. Eventually I’ll look back at the thread and it’s thinking, because like, in some ways, it was stupid, but in some ways it was smart.
I don’t know if the method to debug everything was the most efficient - but it eventually worked. Probably trying to fix the epoch, and also fix the leap years, and also fix the month - all at the same time - overwhelmed it, and my guess is there was no confidence in itself.
I’m talking too much about the AI here for code I didn’t do in a dev log. But anyway, it brute forced tested leap year handling, (it also mentioned “leap seconds” which I’m not dealing with.. so I’m not sure what the heck it’s doing), but eventually all the testcases did past.
It also did suggest using llround to fix some precision issues, like sometimes conversions being off by 1 second. This is actually not a terrible idea, and I may make a build-time config option for this. Since it was successful, I also asked it to import all its scripts in a test suite - which it wasn’t going anywhere for, so I decided to stop wasting energy and stop it there. Eventually I may come back to it.
So it spent at 187K tokens - and even though it did seem to go nowhere, it eventually kind of.. worked? And mkmarstime behaves properly now. I was worried the AI would just try to focus on “solution that solves testcases” instead of “solution that does actual behavior”, but the resulting diff is justified (I even had it check that the output produced was the same in its loop vs closed form intercalation).
Vibe coding, no. But vibe debugging? Well, maybe there is something to be said about it..
Wow, what a sprint!
Part of the C date and time functions is mktime. It takes a struct tm object (which is a “broken down” time structure), and converts it into the time_t format (seconds since 1970). For libspacetime and the Darian Calendar, we need mkmarstime to track time on Mars.
In some ways, this ended up not being harder than I thought it was, and in some ways it did.
In the Gregorian Calendar, we have leap years every 4 years - except when the year is divisible by 100, unless divisible by 400. Yeah, it’s confusing.
The Darian Calendar, in it’s wording from the website and also Wikipedia confused me. I thought Intercalation was the length of a year, but no, it’s actually just getting the # of leap years that have passed at a given year number.
Darian Calendar rules are actually, every odd numbered year is a yeap lear, except for decades (10, 20, 30). Exception to that is if the year is divisible by 100, and again for divisible by 1000.
In the commented code, you can see how I put down my train of thought, but ended up coming to what the actual source was anyway.
Basically, you find the number of leap years, multiply by 669 days, then non-leap years, multiply by 668.
Fun fact: Glibc in its tm structures, according to POSIX specs, starts the year at 0. I believe it is because it could make those numbers fit in a smaller data structure. For us, the Darian year is 221. I guess I could maybe try to shave it down? But these are things I’ll have to decide before any release.
Every month has 28 days, except for the last month in each “quarter”, which has 27.
You could calculate this, but Glibc, for the Gregorian Calendar actually has a static table that lists the amount of days since each month starts. This way it doesn’t have to do the multiplication every time.
I was doing it manually, and I was going to write out the expressions - 281, 282, etc. but it got complicated when also having to add like, 287 + (271) and I didn’t know which I was on track for. By using the example website, I got the numbers by manual conversion.
The table makes sense in Glibc for another reason: The leap day in the Gregorian Calendar is so early in the year, it shifts every day forward by one.
In the Darian Calendar, the leap day is actually at the end of the last month of the year. So we don’t need to store duplicate entries, or actually really worry about it for this function. (Not sure whether it is my job to handle invalid values). But basically if the current year is a leap year, we can still work as normal, and then just add on the day as requested. Nice!
Days, hours, minutes, seconds are all as normal.
The Darian Calendar epoch actually begins in 1609. But.. the Mars Sol Date, which is unofficial but even used in NASA’s Mars24 program, has its “0” date at Dec 29 1873. So we need to subtract to make up for the difference.
If you use dates before MSD 0, you get negative mars_time_t values.
I couldn’t figure out what the epoch meant, until I realized it was just MSD 0. I also found that marsclock.com’s “midday” is actually off by about 177 seconds. But I calculated MSD 0 to be what you see in the code (I need to save characters so I can submit this), and off I went.
Yeah, this needs tests. Even more than what I put in for this devlog (MSD 0 and Unix Epoch 0).
Unfortunately in Mars -> Earth time, I am losing about an Earth second in conversion. I’ll try to debug this now. But it causes some tests, like the epoch to be incorrect - it returns a mars_time_t of what would be unix epoch -1.
The genius of how Glibc handles the months was probably the coolest part. Most challenging, the leap year intercalation for sure.
Now, onto more test suites, and more functions.
Because I’m going out to see family today - might as well get something done. I was going to add “mktime” but I realized that I’ve done Mars to Earth but not vice versa. Besides, I need an “epoch” to start time from.
Technically, I already had done this with mars_time(), I just needed to do it with any timestamp. I did “0” (January 1, 1970, midnight) locally to verify, then chose the Mariner 9 enter orbit as a testcase.
I will note, Wikipedia shows MSD (Mars Sol Date) but also Mars Julian Sol. I wonder if I’ll have to do that later.
I also will probably need to organize my tests/Makefile.am for further testcases. It will grow once I start adding time tests and not just MSD.
I’ve known the Darian Calendar existed, and my goal is to spend my time over the summer to try to make it as close to system level support as possible. This includes trying to get it at kernel-level - which is another project.
Anyways, libspacetime is basically time.h but for other planets. I want it to be portable and work on as many systems as possible. For this reason I setup a GNU Autotools project (autoconf, automake, that fun stuff).
Now, the Darian calendar is not standard, but C date and time functions are. Also, timekeeping is actually complicated. So I’m kind of.. mimicking the standards, and I may not get there in terms of implementation for a while, but I try my best.
Using some sites:
And after fighting with type versions, I eventually got a current unix time, represented in Martian seconds, in a way that you can get to the current Mars Sol Date (MSD).
Uh.. I’m not a mathematician. How does the timekeeping and conversions work, I honestly don’t know off hand. This will prove to continue being a challenge… especially because like, “ok, so what is our “epoch” for mars? Still Jan 1 1970 on Earth? But the existing proposed calendar conversions use this date instead?” - lots of fun in the future.
Okay, basically pre stardance was just mars_time and the initial project.
For test runners, there are options - TAP, DejaGNU, etc. But this isn’t Meson where you can setup test targets.
Autotest is kind of weird (that seems to be the general consensus). If you are testing executable programs, it’s great, because you can get a fixed output. But at the base level, it’s really just a bunch of shell scripts.
So in test “setup”, I want to compile my “tests”. But a user building the library may not have libspacetime headers or library installed, so we need to not only build a C target (which in itself is weird, the GNU Autotools docs for test suites doesn’t suggest this as far as I know), but we need to link against the library built in the src folder of the project and also include its headers. Kinda janky.
The lack of useful documentation examples is what I spent a lot of time on. There is also the thing with atlocal, which I think is like.. empty? But docs suggest it should be generated?
package.m4 files exist now, which I did a great job filling out now that I look back at it -_-
when it comes to actual test checks (testsuite.at), apparently it compares output, not status code.. so I have to like, verbatim tell “I expect this output”. So there goes printing out the status of the current times I am testing.
Also it is so specific, like I can’t use a newline character in the printf in the test executable.
The print is useful because a round conversion from Earth -> Martian -> Earth results in a one earth second loss. I don’t know if it’s because of floating point precision or because of the time it takes to execute the conversion from the processor.
Ideally, the application can ask the kernel for the time and it uses its own clocks - which can use a counter chip, and using the amount of increases per a set of time, can figure out how long a “cycle” is and keep time that way. That was sort of the goal of my kernel “spacetime” support, but I wanted it to show up in lsclocks, and after all clocksource is like, a hardware abstraction, not an actual clock. Idealy a clocksource could just have a lower multiplier, but the kernel still uses the best clocksource which certainly won’t be the martian one.
Anyways, use janky tools (Autotest framework), get janky results. Cheers to more date and time functions and tests.
I started this before Hack Club, so it’s worth the explanation. Since Mojang announced they would remove the “1.”, I felt like it was too iconic to be removed. So, I setup a mod template and combed through MC version code. It turned out that there is the SharedConstants class, which contains a “WorldVersion” and a human-readable “name” string. We just need to add “1.” to the start of the string. That is basically it.
Now since WorldVersion is an interface, I took the SharedConstants CURRENT_VERSION variable and basically just made another WorldVersion with my modification.
Finally on the client side, I have to call the SharedConstants.setVersion method. I thought I could just do this without the mixin but the game won’t let you change constants like that. But, if I don’t call the setVersion (that I modified to include the “1.”), nothing will happen. Probably because the setVersion is called before any mods are loaded (it’s like, one of the first things the game does as you launch it - possibly before the window is even made! I remember thinking the LAUNCHER did it via some java executable flag, but I’m not sure).
A few weeks ago, I saw a bug https://github.com/JPeisach/OneDot/issues/1 about how Realms would say “client incompatible!”. Now I don’t use realms, but as hypothesized in the bug report, the RealmsClient in Vanilla does indeed send the client version number to the realm for authentication. And of course, with the “1.” at the start, that makes a version mismatch.
The nice thing about the SharedConstants solution is that it appeared everywhere - the window title, the title screen on the lower left corner, the F3 menu - but this is a case where we don’t want the version number to change.
I quickly traced down the call to WorldVersion.name. Thankfully in the code, Mojang abstracted all the execute functions that send the version in the request to the same function. After fighting around with Mixins and figuring out where best to inject my replaced String, I got a proposed patch. (Thankfully with the IntelliJ Minecraft modding plugin, it could provide a Mixin reference. But until then I forgot about it and tried to manually play with it. I looked at docs, and tried redirecting the method call, or injecting into the returns - I settled on ModifyArg). For simplicity, I just substringed the first three characters instead of trying to play with indexOf in case another “1.” appears in the version number.
Oh yeah, and that requires another mixin. Unlike the SharedConstants mixin, it actually lies in the client only, so there has to be a client mixin package now. No biggy.
I don’t have access to realms, I need someone else to test to make sure they can still reach it. But this fix should work. If you have access to a realm, please let me know. (I’ve also contacted the initial bug reporter).
So the jar actually only contained the code for the mod loader client, and not the common code. Oops.
Thanks to some help in the Architectury discord, I found that I needed to upgrade to another “shadow” gradle plugin (on another ID) and also apply some changes. Now everything is correct.
Since I messed up, I made a new release (1.0.3), and it also gave me the chance to update the repository information since I changed my GitHub name from ItzSwirlz to JPeisach (I think I’d prefer “jpeisach” but I don’t know if I can be that specific - regardless, the url is case insensitive).
After skipping 26.1 because Architectury (mod loader abstraction library) didn’t have a version available (as far as I knew), and since the update was well.. nothing interesting, since one now exists for 26.2 I took the time to port SLSTOOF (So Let’s Set The Ore On Fire) to 26.2.
SLSTOOF is a mod that adds the other kinds of fire types. In real life, if you light copper on fire, the color actually ends up being a little bit light-blueish. For example, Copper Torches. I think Mojang did this in education edition, but not in Java. They would eventually add it (and I would need to create “aliases” to automatically migrate world blocks from the mod’s copper torch to the game’s copper torch, but that happened pre-Star dance). Iron also lights a bit more “yellowish”, and also, since redstone torches are red, I made redstone fire “red”.
Aside from the “fire block” that is made depending on which surface you light the fire on, I also took the time to place in the missing torches and campfires. I also made the redstone campfire emit signal :)
You can also see that I put in GameTest tests. For now, I just did basic flame ignition. In the future, I could add more to ensure things like crafting or item drops work. But believe me, this is useful for when you have to make a minor upgrade and don’t want to have to start the game to check if anything broke.
The main headaches were:
Just some manual configurations like using JAVA_HOME to specify which Java version to use. It took me an embarrassing long time to realize that the “unsupported major class version” errors were because of this.
Gradle was updated to 9.5.0.
Architectury Loom (fork of Fabric Loom, which I think helps manages dependencies and mod toolchain but I forgot), had also gotten updates. Same with the Architectury Gradle Plugin. Since obfuscation is no longer a thing, I had to tell gradle to stop complaining about the lack of “mappings” dependency, and I also had to bounce around snapshot versions of Architectury Loom until I found the solution for porting my project, because official docs for porting had not been updated yet and neither had the mod template generator.
By combing through Discord messages on the Architectury Discord, and lots of trial and error, I eventually got things to work.
Access Wideners: named -> “official” (also required a change in gradle config)
RenderTypeRegistry was used to ensure that the blocks that do not take up entire 1x1x1 spaces, like torches and campfires, don’t get registered as such. Otherwise they have a black border above the actual texture.
It seems that this is no longer needed, as I can just set the Campfire BlockEntityType list to include my custom campfires - and everything else seems to not be affected. Go Mojang refactoring, I guess?
Anyways, it worked out. I think another class was renamed in the Fabric/Neoforge side of things, but I tested everything and all is good.
After missing 26.1, I was a little worried - I thought that I may have to switch to Balm, another mod loader abstraction library, in hopes that it would be more up to date. But, probably since college has ended, people had time to work on it and I am now able to deliver only two days after release, which is pretty good.
The ported versions of the mod are now available on GitHub and Modrinth for Fabric and NeoForge.
Happy porting and world upgrading everyone! (Oh and thanks Zed for not counting the like, hour I think, I spent fighting with Gradle before I switched to IntelliJ, maybe rerunning the Hackatime install script did something, but nevertheless it’s about 37 minutes of actual “code” time).
Phew, another dev log!
For me, I had to figure out, what code should be part of the API, and what is the purpose of the coordinator?
I compared to ha-wiiu (https://github.com/wiiu-smarthome/ha-wiiu/blob/main/custom_components/nintendo_wiiu_ristretto/coordinator.py) as an example, and decided that:
So once I figured that out, it was off to me playing around with pywikibot to see how I could get site-specific user contributions. It seems that I will just have to count items in the contribution list, but for global site info (so, like all Wikimedia projects), it has its own accessible field.
So now I have a “0” in the sensor reading “Contributions”, which is correct because I have not made any edits to Test Wikipedia. I should soon be able to hook things up to other MediaWiki instances and see actual values!