-- Leo's gemini proxy

-- Connecting to bbs.geminispace.org:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini; charset=utf-8

Converting Unix timestamp to date/time


Without any outside libraries.


All we have is a Unix timestamp, seconds since Jan. 1, 1970.


I'm looking for a minimalistic solution for my tiny nForth, but really curious if anyone has tricks up their sleeve for this kind of a task. I'm willing to ignore leap seconds for now.


So far I got the time part: add timezone in seconds, divide by 86400 to get days, and use the remainder for time in seconds. The rest is trivial, dividing by 60 for minutes and 60 for hours, and keeping remainders.


The date is harder, due to leap years. I was considering adjusting the date by 68256000, to rebase the seconds to March 1, 1972. This puts the leap day on the last day of the 4th year; we can count by year-quads consisting of 365+365+365+366, or 1461 days, then zeroing in on the year, and figuring out the month by successively subtracting month lengths... But then there is day of week, maybe I don't even need that.


So any clever ideas? I feel like the task is old enough that some hacker must've come up with a math trick to make it easier.


Some links I came across and should probably look at some more:

=>https://stackoverflow.com/questions/21593692/convert-unix-timestamp-to-date-without-system-libs

=>https://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c?h=v0.9.15

=>https://github.com/python/cpython/blob/54b5e4da8a4c6ae527ab238fcd6b9ba0a3ed0fc7/Lib/datetime.py#L63


Posted in: s/programming

๐Ÿš€ stack

2023-09-10 ยท 8 months ago ยท ๐Ÿ‘ DocEdelgas


10 Comments โ†“


๐Ÿฆ€ jeang3nie ยท 2023-09-10 at 17:37:

Funny but I have a draft gemlog post about doing exactly this in Zig. Maybe I'll dust it off later. One the meantime I've made the repository public of you want to have a peak.


โ€” https://git.hitchhiker-linux.org/jeang3nie/zig-chrono


Not sure how easy the code is to follow if you don't know Zig. I encoded the year as a tagged union which is either a leap year or not, and then you'll just cycle through the years and add up the seconds. When you get to the point where you're in the current year, you have to go by months, then days, hours, minutes.


It's possible there's a faster way but the libc implementations I've looked at basically do this, so perhaps not.


๐Ÿš€ stack [OP] ยท 2023-09-10 at 20:47:

@jeang3nie: thanks, but I can't open the link -- it takes me to a login screen.


๐Ÿฆ€ jeang3nie ยท 2023-09-10 at 22:26:

Ok, give me a few minutes. I'm going through the code now to get it working with Zig 0.11, and then I'll push it to codeberg instead of my home server.


๐Ÿฆ€ jeang3nie ยท 2023-09-10 at 22:58:

I guess I posted the link incorrectly the first time. I fixed it, but I also mirrored the code on Codeberg.


โ€” https://codeberg.org/jeang3nie/zig-chrono


You've got the right idea with what you described so far. There's a few things you have to account for that are a little sticky.

Leap years change the number of days per year, and so also the number of seconds

Days per month is inconsistent, and you need to know if it's a leap year or not once you get to where you know what year your timestamp falls

Calculating leap years is slightly more complex than I realized. When growing up I thought it was just every four years, but that would slowly skew the seasons. The year must be divisible by 4, but not 100, unless it is also divisible by 400. How's that for fun?


So we know that we have 24 hours in a day, 60 minutes in an hour, and 60 seconds in a minute. From that we can derive a constant seconds per day = 24 * 60 * 60. We start at the unix epoch 1/1/1970 which corresponds to 0 seconds and increment the year (assuming a positive timestamp), adding the number of seconds in that year (364 * secs/year or 365 * secs/year if a leap year), until the result would go past the timestamp we want to convert. At that point we know what year the timestamp falls in. From there, we do the same for months but we're looking at seconds per month, which varies depending on what month it is, and in the case of February also whether it's a leap year or not. Then on to days, hours, minutes and seconds. It's just basically a big iterator.


The conversion happens in the function DateTime.fromTimestamp.

โ€” https://codeberg.org/jeang3nie/zig-chrono/src/commit/2d2b99c954d21edd5a75f597014ec15ce6042065/src/main.zig#L86


I realized after my first reply that the post I had been working on was for my big web blog, so it's nowhere near in shape for gemini. But I can walk you through any parts that are unclear. I did this implementation because I had already done it in Rust for my Misfin project and found it interesting enough for a blog post, so I've implemented it twice now. Pretty sure I could translate it into a few other languages without much trouble.


๐Ÿฆ€ jeang3nie ยท 2023-09-10 at 23:03:

Also, about leap seconds. The big takeaway is that so long as you are working in a hosted environment (your code is running within an operating system and getting it's time from what the OS says is system time) you can ignore leap seconds, because the OS handles it for you. The usual method is that if a year is supposed to get a leap second, the final second of the year just gets repeated. So on Dec 31st of that year you'll get two seconds which have the same timestamp. It's not perfect but it's the convention, and it has the benefit that as a programmer you just don't have to care unless you're working on kernel code.


Of course, in true asshole fashion, both Google and Meta decided to get fancy and in their systems they smeared the extra second out over all of the seconds in the day the past couple times it came up. Because why care about interopability?


Sorry for the long windedness, I just find the subject fascinating.


๐Ÿš€ stack [OP] ยท 2023-09-11 at 01:48:

Interesting. It seems like my way is a lot easier. Since the only weird year between 1970 and 2037 is 2000, and it happens to be a leap year, we can ignore the 100/400-year exception - it is just like the rest of the leap years. I already have days since 1970, so I can continue the calculation in days.


As noted previously, I adjust to March 1972 as the base year, to shift the leap day to the last day of a 4-year bundle. I can then divide by the number of days in such bundles, yielding number of days within the final quad as remainder.


The rest of the calculation is division by 365 to find the year within a quad (which also detects if we won the leap year lottery),


Then, to figure out the months, I go into a loop subtracting days in each month. That is the only iteration required.


๐Ÿš€ mbays ยท 2023-09-11 at 05:30:

So you plan to be part of the Y2038 problem?


๐Ÿ‰ gyaradong ยท 2023-09-11 at 06:43:

some notes:


- a country went into daylight savings and never came out

- countries and states routinely change the dates when coming into or out of daylight savings.

- a country one skipped a week or rewound a week. can't remember which.

- a small island went from one side of the international date line to the other. I think they are now beyond 12 hours forward.


I'm never touching a time library.


๐Ÿฆ€ jeang3nie ยท 2023-09-11 at 15:03:

Crap, don't get me started on daylight savings time.


๐Ÿš€ stack [OP] ยท 2023-09-11 at 17:05:

I am passing the buck to the user, of course. A variable will keep the timezone adjustment offset, and whoever cares about this will set it to the right amount.


At this point, it is very likely that I will be the only user anyway.


X

-- Response ended

-- Page fetched on Sun May 19 14:34:46 2024