Skip to content

Instantly share code, notes, and snippets.

@MichaelDimmitt
Last active September 19, 2024 14:10
Show Gist options
  • Save MichaelDimmitt/c0141aa7ef73a614871b6111510c7dfb to your computer and use it in GitHub Desktop.
Save MichaelDimmitt/c0141aa7ef73a614871b6111510c7dfb to your computer and use it in GitHub Desktop.
What would a history info website look like?

In the immortal words of Rick and Morty:

Don't ... mess ... with ... time!!

Response: I will mess with time!, I will mess with time.

image

Disclaimer, all dates were run in macOs Chrome browser console. Your mileage may vary.

It started with a simple question I asked myself around dates.

I often find myself working on some information system website. ERP, DSS ... or some other system and I find myself storing and presenting data with date information. For the most part you can use the javascript date object when dealing with dates. You can also reach for js date libraries like dayjs, luxon, momentjs, datefns ... etc while you wait for the DateTime change called temporal to get to stage 4 and then ship in the browser, to which temporal actually looks done.

Anyway, I asked myself a question:
What would a history website look like that needed to show data with dates before the unix epoch: 1970?

Join me on a fun journey getting to year zero and beyond with Javascript Date! 🚀

TLDR: I could have saved a lot of time by just reading the first paragraph of the mdn date docs.

A Date object can represent a maximum of ±8,640,000,000,000,000 milliseconds, or ±100,000,000 (one hundred million) days, relative to the epoch. This is the range from April 20, 271821 BC to September 13, 275760 AD. Any attempt to represent a time outside this range results in the Date object holding a timestamp value of NaN, which is an "Invalid Date".

TLDR: Dates can range from April 20, 271,821 BC to September 13, 275,760
^ Yes, that is a date. No, we will probably not be alive to see it.

Getting to pre 1970

I started my quest to go back in time by instantiating a new Date() which resolved to todays Date.
Next, I passed new Date(0) which returned the unix epoch: 1970

Wed Dec 31 1969 19:00:00 GMT-0500 (Eastern Standard Time) {}

At this point I started doing some research and after reading a css-tricks article, everything-you-need-to-know-about-date-in-javascript I started using the date argument syntax: new Date(2019, 5, 11, 5, 23, 59).

Okay, what happens when we change this to the quarter century?

new Date(1950, 0, 0, 0, 0, 0)`
// output: Sat Dec 31 1949 00:00:00 GMT-0500 (Eastern Standard Time)`

Paydirt!, Now lets continue going into the past...

Lets get to year zero! We know new Date(0) gives us the unix timestamp:

new Date(0)
Wed Dec 31 1969 19:00:00 GMT-0500 (Eastern Standard Time)

Is that the same for this new input?

new Date(0, 0, 0, 0, 0, 0)
// output: Sun Dec 31 1899 00:00:00 GMT-0500 (Eastern Standard Time)

Hey, wait ... 1899 is not year zero. Well that was unexpected. 🙀

Can we get to a value less than 1900?

new Date(1850, 0, 0, 0, 0, 0)
// output: Mon Dec 31 1849 00:00:00 GMT-0456 (Eastern Standard Time)

Success!

year 10:

new Date(10, 0, 0, 0, 0, 0)
// Fri Dec 31 1909 00:00:00 GMT-0500 (Eastern Standard Time)

1909 is not year 10!! 👻

Maybe it defaults to 1900 if no leading zeros?

new Date(0010, 0, 0, 0, 0, 0)
// Tue Dec 31 1907 00:00:00 GMT-0500 (Eastern Standard Time)

^ now for no reason it is 2 years sooner! probably due to some octal or binary value... 😮‍💨

Maybe it is an issue with the 1's? Lets try year 2:

new Date(2, 0, 0, 0, 0, 0)
Tue Dec 31 1901 00:00:00 GMT-0500 (Eastern Standard Time) {}

Nope, 1901 is not year 2 ad - gregorian calendar.

Time to revisit... 1950 and 1850. Does year 200 work?

new Date(200, 0, 0, 0, 0, 0)
// Tue Dec 31 0199 00:00:00 GMT-0456 (Eastern Standard Time)

Success!

100?

new Date(100, 0, 0, 0, 0, 0)
Thu Dec 31 0099 00:00:00 GMT-0456 (Eastern Standard Time)

99?

new Date(99, 0, 0, 0, 0, 0)
Thu Dec 31 1998 00:00:00 GMT-0500 (Eastern Standard Time)

🤦‍♂️ ... I see what is happening. Date 99 assumes you wanted 1999. Date 10 assumed you wanted 1910. My timezone shifting the date to the year previous made that a little more difficult to discover.

Do negative values work?

Lets try to see if we can get to BC

new Date(-2, 0, 0, 0, 0, 0)
Wed Dec 31 -0003 00:00:00 GMT-0456 (Eastern Standard Time)

Actually, year -0003 looks correct after the timezone check!

// Try positive:
new Date(2019, 5, 11, 5, 23, 59)
// Tue Jun 11 2019 05:23:59 GMT-0400 (Eastern Daylight Time)

and 
// Try negative:
new Date(-2019, 5, 11, 5, 23, 59)
Thu Jun 11 -2019 05:23:59 GMT-0456 (Eastern Daylight Time)

Looks like negative works. It shows a negative date with the exact format we specified.

Lets circle back to the year 0 and year 99 range:

new Date(0, 0, 0, 0, 0, 0)
// Sun Dec 31 1899 00:00:00 GMT-0500 (Eastern Standard Time)

new Date(99, 0, 0, 0, 0, 0)
// Thu Dec 31 1998 00:00:00 GMT-0500 (Eastern Standard Time)

Houston, we have a problem:

At this point I was thinking... how do we solve for years in range 0 to 99? Those years seem important for a history website regardless of your belief system.

Well, lets take stock,

  1. I have broken out of 1970
  2. I can go back in time to BC values (... atleast I think that value is valid.)

Would these dates even work? Would I be able to post them to a backend and save them to a database? I knew from some research that computers used epoch time and that there could be some issues like Epochalypse when the 32 bit int that represents that value overflows.

What the heck would the timestamp be for epoch time new Date(0)? I knew other dateTime libraries had a timestamp function. Luxon was the first that came to mind because I knew I could go to the browser console on that website and use the library.

What is the timestamp for today?

DateTime.fromJSDate(new Date()).toMillis()
// 1726709985886

Whoa, that is a big number

What is the timestamp for epoch time?

DateTime.fromJSDate(new Date(0)).toMillis()
// 0
DateTime.fromJSDate(new Date(1)).toMillis()
// 1

Oh super cool, so I was actually passing in the timestamp to get the date to behave that way and when I call the luxon toMillis() function it gives me the timestamp back. Side note: in property testing this is actually called a symmetric property!

Ok... so what is a timestamp for a date previous to 1970?

DateTime.fromJSDate(new Date(1950, 0, 0, 0, 0, 0)).toMillis()
// -631220400000

It is just a negative timestamp! 🎉

Maybe year zero is possible!

Finding year zero, attempt 1.

new Date(100, 0, 0, 0, 0, 0)
// Thu Dec 31 0099 00:00:00 GMT-0456 (Eastern Standard Time)
DateTime.fromJSDate(new Date(100, 0, 0, 0, 0, 0)).toMillis() // last known good value
// -59011527838000

-59011527838000 is more negative than -631220400000, so it looks like we went in the right direction!

After a good amount of tweaking I ended up at the following value:

new Date(-62122532638000)
Fri Jun 01 0001 00:00:00 GMT-0456 (Eastern Daylight Time)

Which is technically wrong due to my timezone offset but I liked looking at all the zeros!

At this point you may be wondering like I was, "how the heck would my history website be able to reliably add dates in this range?

Finding year zero, attempt 2

It looked like a timestamp was just miliseconds which were 1 second === 1000 miliseconds. Lets test it out!

new Date(0)
// Wed Dec 31 1969 19:00:00 GMT-0500 (Eastern Standard Time)
new Date(1000)
// Wed Dec 31 1969 19:00:01 GMT-0500 (Eastern Standard Time)

Hey, it worked!

Well... hmm. Can I use multiplication and quickly get to day zero in a maintainable way?

const year = 31554000000
const months = 86400000
const minutes = 60000
const seconds = 1000
const zero = 1 // millisecond value, returns unix timestamp zero when passed in.

new Date(zero) // returns unix epoch 1970
new Date(zero - (1970*years) - (67 * months) - (544 * minutes) + (2 * seconds)

Day zero is: 1970 years, 67 months, 544 minutes, and -2 seconds away from the unix timestamp.
Hey, that was not too bad, you jump to the year... then adjust the months. Then adjust the minutes. and then tweak seconds. You can also pass negative seconds to have nice rounded minutes.

So a human could do it by trial and error with a reasonable 4 hops. But is there a better way where we consider leap years and automatically get to the solution in one try?

At this point it would be worth pointing out that we do not need to cover leap years for all 1970 years. Only for the 99 year range from year 0 to year 99. Some quick math and checking on a website tells me there are 24 leap years to consider.

However, it is getting late... I am going to let you write the equation. But it is something like:
Year 0 would require the full 99 days to be evaluated for leap days.
Year 5 would require 99 - 5 days to be evaluated for leap days.

Take that result / 4 to get the leap day value.

Use this for all other dates outside that range:

new Date(99, 0, 0, 0, 0, 0)
// Thu Dec 31 1998 00:00:00 GMT-0500 (Eastern Standard Time)

(suddenly)... an abrubt discovery occurs ...

Roll on the floor Laugh out loud. 🤣

So ... this works.

new Date(100, -12*99, 0, 0, 0, 0)

So use that date format for everything and then

new Date(100, (-12*99)+your-months, 0, 0, 0, 0)

For dates within that 99 year range.

And then, to the future:

Also future dates just work using the date arguement format:

new Date(2039, 5, 11, 5, 23, 59)
// Sat Jun 11 2039 05:23:59 GMT-0400 (Eastern Daylight Time)

Will servers be able to save my dates that are from the past or from the future?

As stated at the beginning of the article,
The Javascript Date() can range from the year:
April 20, 271,821 BC
to
September 13, 275,760 AD

That is pretty cool! And any sane person will never hit that date. The more realistic date to worry about is Epochalypse: 2038. As stated in this wikipedia article - Year_2038_problem there are many solutions already in place that support 64 bit integers. Ruby solved in 2010 for time_t, Postgresql solved in 2002-02-04, ... etc.

I think javascript Date() uses a 52 bit integer (but this is probably wrong I will update later.)

And the good news is... if they do not have a solution just send them a timestamp and handle the times yourself client side. And have the database like Postgresql use a 64 bit signed integer to save any timestamp value you may send over.

Regarding Temporal:

Temporal has a concept of Eras and I think we will be able to refer to dates pre-dating 1970 through that date library.

Temporal actually looks done: https://github.com/tc39/proposal-temporal/milestone/11

On that list: #2628 is done #2863, #2864 have a pr up for that looks good to me #2940

and #2865 looks like a wont do. or that it will also be resolved with #2940

So then we just have to wait six months for the standards board to meet. And then wait for browsers to implement.

And then convince everyone to use temporal instead of using dayjs, luxon, momentjs, datefns ... etc. What would be interesting is if the date libraries started removing features if they already are being shipped in the browser once temporal lands.

(I was not involved in any of the temporal stuff but I just checked. They thought it would be done in 2021 tc39/proposal-temporal#1079 (comment)) But the roadmap posted looks mostly complete so fingers crossed! 🤞 )

And with that, now it is time for me to go to bed 😴

Links:

  1. https://en.wikipedia.org/wiki/Epoch_(computing)
  2. https://en.wikipedia.org/wiki/Year_2038_problem
  3. https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment