December Adventure Day 03
ROMs and Time Zones
Continuing my Psion-themed December Adventure, I returned to Sunday’s sorting of ROMs, and chipped away at the OPL take on Thoughts I started yesterday. I’m also rapidly learning that one of the advantages of slowing down a little and writing up my daily adventures is getting to share them with others and, quite wonderfully, receiving realtime feedback, suggestions and contributions.
ROMs
I spent some time adding more Psion ROMs to explit7/Psion-ROM—Sienna, Series 3c, and Series 3mx this time around. Thanks to feedback from Alex, I’ve also added MD5 checksums and EPOC16 versions (the latter really helps differentiate between device variants and language variants). There are still a few gaps in the table as I’ve yet to find for myself where the EPOC16 version comes from but hopefully I can flesh that out over the next couple of days.
The additions are as follows:
Psion Siena
Device | EPOC16 Version | ROM Version | Language | Filename | MD5 Checksum |
---|---|---|---|---|---|
Psion Siena | 3.70f | 4.20f | English | vine_v4.20f.bin | 242e80fdbf9b353a05f6ff4d1db1c769 |
Psion Series 3c
Device | EPOC16 Version | ROM Version | Language | Filename | MD5 Checksum |
---|---|---|---|---|---|
Psion Series 3c | 3.91f | 5.20f | English | oak_v5.20f_eng.bin | 3c1a079f53c00916e8d0dc11b35a0390 |
Psion Series 3mx
Device | EPOC16 Version | ROM Version | Language | Filename | MD5 Checksum |
---|---|---|---|---|---|
Psion Series 3mx | 4.08f | 6.16f | English | maple_v6.16f_uk.bin | 64572cc3522447179d1e6f3b8fb45360 |
Psion Series 3mx | 6.20f | French | maple_v6.20f_fre.bin | 1b367e2fb862545cf420ff74a7f85ea7 |
Looking over the listings of ROMs have been dumped for these devices, it’s abundantly clear that we’re missing many localizations—I understand we know of at least English, French, German, Italian, Flemish, and Dutch variants of the 3c, so we’ve a long way to go. If you have a non-English version of these devices, please get in touch by dropping me a mail or joining us in Discord. We’d absolutely love to talk you through the process and would be eternally grateful!
As an aside, I love how the ROM files use the device codenames and am tempted to surface those as an explicit column. I’ve also just learned that we have a newly minted Series 7 ROM dump, so that’s on the list for tomorrow.
Thoughts-Lite
Fabrice Cappaert spotted a bug in my ZPAD
implementation (see yesterday’s notes)—I was using an IF
instead of a WHILE
-loop when checking to see if my target string was long enough, meaning it would never have worked for pads longer than 1. (Just goes to show that even for small programs, it’s worth unit testing things.) Thankfully, he also offered up a significant simplification which we both suspect will work more efficiently given the costs of certain operations in OPL:
PROC ZPAD$:(value&,length%)
REM Left-pad strings with '0' up to a length, length%.
RETURN RIGHT$(REPT$("0",length%)+NUM$(value&,length%),length%)
ENDP
This approach also allows me to avoid using a local fixed-size string which makes it far more flexible than the previous implementation which implicitly limited length%
to 10.
Perhaps most-crucially however, was Fabrice’s help with OPL time zone wrangling: the macOS version of Thoughts stores the UTC offset in the ISO 8601 timestamp and this is functionality I really want to preserve in the OPL version1. Yesterday, I was starting to get a little nervous about my ability to implement this as I couldn’t find built-in functions to get the user’s UTC offset, even though it’s clear from the World app that the Psion knows where I am and my time relative to UTC.
Thankfully, Fabrice pointed me at one of the first-party after-market native libraries—SystInfo.opx—which provides SIUTCOffset&:
. This returns the UTC offset in seconds and, a few calls to MOD
and IABS
later, I was able add the UTC offset to my ISO 8601 formatted date:
PROC ISO8601$:(datetime&,utcOffset&)
REM Return an ISO 8601 formatted date and time with UTC offset.
REM Result is guaranteed to be 25 characters long.
LOCAL year&, month&, day&, hour&, minute&, second&
LOCAL offsetSign$(1), offsetHours&, offsetMinutes&
LOCAL result$(25)
REM Extract the datetime components.
year&=DTYEAR&:(datetime&)
month&=DTMONTH&:(datetime&)
day&=DTDAY&:(datetime&)
hour&=DTHOUR&:(datetime&)
minute&=DTMINUTE&:(datetime&)
second&=DTSECOND&:(datetime&)
REM Extract the offset components.
offsetHours&=IABS(utcOffset&/3600)
offsetMinutes&=MOD&:(utcOffset&/60,60)
IF (utcOffset& < 0)
offsetSign$="-"
ELSE
offsetSign$="+"
ENDIF
RETURN NUM$(year&,4) + "-" + ZPAD$:(month&,2) + "-" + ZPAD$:(day&,2) + "T" + ZPAD$:(hour&,2) + ":" + ZPAD$:(minute&,2) + ":" + ZPAD$:(second&,2) + offsetSign$ + ZPAD$:(offsetHours&,2) + ":" + ZPAD$:(offsetMinutes&,2)
ENDP
It’s not all been plain sailing though. In addition to storing an ISO 8601 date and time with a UTC offset in the note metadata, I wish to name the file using a UTC date and time to ensure they sort correctly on the file system. For example, a note published at 2024-12-02T16:05:06-10:00
should be named 2024-12-03-02-05-06.md
. While I had expected this to be simple (subtract utcOffset%
from datetime&
and generate a string similarly to the code above), it seems OPL date/times are a little bit magical.
Consider the following code:
LOCAL timestamp&, offset&, utcTimestamp&
LOCAL year&
REM Get the current timestamp and offset.
timestamp&=DTNOW&:
offset&=SIUTCOffset&:
REM Calculate the UTC timestamp.
utcTimestamp&=timestamp&-offset&
REM Extract the datetime components.
year&=DTYEAR&:(utcDatetime&)
While you might expect this to work, it fails with ‘Invalid arguments’ as utcTimestamp&
is apparently no longer a valid date/time, even though both timestamp&
and utcTimestamp&
both behave like longs when passed to many functions. I can only assume they’re backed by a magical object under the hood. Hopefully I can find a way to get the UTC timestamp directly, or initialize a new date/time with a long, but that’s one for another day.
-
The UTC offset allows notes to be displayed in note-local time, UTC time, or the reader-local time. ↩