Remix.run Logo
QuadmasterXLII 5 days ago

I ran into date heck recently in a medical setting for storing birthdates. Eventually I settled on the idea that a birthdate isn’t a physical time, it’s just a string. We can force the user to enter it in the format 02/18/1993 leading zeroes and all, and operations on it other than string equality are invalid. We’ll see if this survives contact with the enemy but it’s already going better than storing and reasoning about it as a point or interval in time and people’s birthdays changing when they move timezones.

kaoD 5 days ago | parent | next [-]

I like how Temporal[0] does this. What you were dealing with is Temporal.PlainDate[1], i.e. a date with a calendar associated but no time or timezone (might be due to being implied but also might be irrelevant, like in birthdates).

Temporal has other cool types, each with distinct semantics:

- Instant: a fixed point in time with no calendar or location. Think e.g. "the user logged in at X date and time" but valid across the world for any timezone or calendar system. This is what we usually use "Unix UTC timestamps" for.

- ZonedDateTime: like an Instant but associated with a particular calendar and location. Think an Instant but rendered "real" into a calendar system and timezone so the user can see a meaningful time for them.

- PlainDate: already discussed. Think e.g. birthdates.

- PlainTime: think "run task every day at 6:30pm".

- PlainDateTime: like an Instant but associated with a calendar system, but no timezone. Think e.g. what a user would insert in a datetime picker, where the timezone is implied instead of explicitly selected.

- PlainYearMonth: think e.g. "we'll run our reports during October 2025".

- PlainMonthDay: think e.g. "my birthday is June 13".

- Duration: think e.g. "the task ran for 3hrs 30min".

Also see its important concepts[2].

[0] https://tc39.es/proposal-temporal/docs/

[1] https://tc39.es/proposal-temporal/docs/#Temporal-PlainDate

[2] https://tc39.es/proposal-temporal/docs/timezone.html

Terr_ 4 days ago | parent | next [-]

I hope that as time goes on software design get better about modeling things that are guesses, conjecture, etc. rather than Absolute Facts.

As-is, we assume lots of things are facts and we just hope it's true enough to avoid problems. (Starting with the business requirements. :p )

keeganpoppen 5 days ago | parent | prev | next [-]

yeah, it was a long (and painful) time coming, but i think the temporal api finally basically nailed it. you know a library is good when you learn something about how to think about the problem just from how the code/api is structured.

benreesman 4 days ago | parent [-]

Relatedly, std::chrono isnt exactly a beauty, but it did get people thinking about time points and durations and clocks and which operations are valid ways to move among them. Stuff like this is good.

geocar 4 days ago | parent | prev [-]

> a date with a calendar associated but no time or timezone (might be due to being implied but also might be irrelevant, like in birthdates).

It might also be relevant: Ever ask an older Korean person their age?

> Instant: a fixed point in time with no calendar or location. Think e.g. "the user logged in at X date and time" but valid across the world for any timezone or calendar system. This is what we usually use "Unix UTC timestamps" for.

This is not a thing. Those are intervals to some Epoch, maybe taking into account leap-seconds and maybe not. They are not very useful except grossly over long ranges.

> - ZonedDateTime: like an Instant but associated with a particular calendar and location. Think an Instant but rendered "real" into a calendar system and timezone so the user can see a meaningful time for them.

Like when a user logged in at X date and time. They don't do this from no location, but from some location.

> - PlainDate: already discussed. Think e.g. birthdates.

And already wrong.

> - PlainTime: think "run task every day at 6:30pm".

Erm no. You can say 18:30 hours after midnight, or you can say when the calendar says 6:30pm, but these are different things. Imagine the poor fool who wants to run the task every day at "1:30am" and has it run twice on some days.

Bars close in some parts of the world at 30h (30時) to mean 6am the following day.

> - PlainDateTime: like an Instant but associated with a calendar system, but no timezone. Think e.g. what a user would insert in a datetime picker, where the timezone is implied instead of explicitly selected.

No, like a string.

> - PlainYearMonth: think e.g. "we'll run our reports during October 2025".

Nonsense. Also a string.

> - PlainMonthDay: think e.g. "my birthday is June 13".

Your birthday might be on the 29th of January. You cannot do reasonable arithmetic with such things, so it might as well be a string like many of these others.

> I like how Temporal[0] does this.

I don't if you can't tell. This stuff is complicated and I'd like more people exploring it because I don't know The Right Answer™ either, but I know enough to know that every existing solution is wrong in some way that can cause real harm.

kaoD 4 days ago | parent [-]

Just FYI you were downvoted with no explanation because you missed the point in all of these and you're using a smug and off-putting tone which makes it look like only care about "being right" and not finding what "is right".

Also you obviously didn't bother reading the "important concepts" link ([3]).

I was going to assume good faith and reply to each of your comments but it'd probably be a waste of time. As a summary: most of your concerns are wrong due to (1) confusing "timezones" with "location" or "internationalization" (2) confusing internal representations (like Epochs) with what these objects represent as described in the "important concepts" link and (3) just being completely wrong like saying you cannot do reasonable arithmetic with PlainMonthDay or even understand that not every relevant operation is arithmetic (good luck calling `.toPlainDate(2025)` with your string representation).

geocar 3 days ago | parent [-]

> FYI you were downvoted with no explanation because you missed the point in all of these

I think people downvote things they disagree with, and I'm not surprised people who think handling dates and times is beyond them are mad at someone exists who doesn't, because I'm calling out their impotence in a way.

And I understand you don't want to feel impotent, so telling me that you're not going to respond to me is a way for you to reclaim some of that.

> just being completely wrong like saying you cannot do reasonable arithmetic with PlainMonthDay

Then prove it: How exactly do you think you can meaningfully add two PlainMonthDays? What does such a thing mean?

I think you don't know what you are talking about, and you somehow think that means I don't either.

kaoD 3 days ago | parent [-]

> Then prove it: How exactly do you think you can meaningfully add two PlainMonthDays? What does such a thing mean?

Star Wars day is May the 4th. Today is May the 1st. Star Wars day is in 3 days.

I'll stop here.

geocar 3 days ago | parent [-]

You might want to look in a dictionary and see what `add` means.

4May+1May is still not meaningful to me, and frankly I do not believe that it is meaningful to you. I do not think you have thought about what I am saying at all, and think it's shocking that you have an opinion about something you clearly don't understand.

pavo-etc 2 days ago | parent [-]

You might want to look in a dictionary and see what `subtract` means.

4May-1May is meaningful to me, and frankly I do believe that it is meaningful to you. I do not think you have thought about what we are saying at all, and think it's shocking that you have an opinion about something you clearly don't understand.

geocar 2 days ago | parent [-]

> 4May-1May is meaningful to me

Great, but it isn't addition.

> and frankly I do believe that it is meaningful to you.

Well you're wrong.

Consider 1Mar-28Feb: Should this be 1 or 2?

This proves it can't be subtraction (in the arithmetic sense) either.

> You might want to look in a dictionary and see what `subtract` means.

Subtraction is only addition if 4May-1May is equivalent to 4May+-1May which leaves you with an additional problem: What to do about negative plaindates.

> I do not think you have thought about what we are saying at all

I've probably thought about it too much at this point. You don't even know how arithmetic works, so I think if you have anything to say it will be too difficult for me to understand.

8organicbits 5 days ago | parent | prev | next [-]

> in the format 02/18/1993

Is this DD/MM/YYYY or MM/DD/YYYY? I can tell from the 18 that it's the latter, but that convention isn't universal. I'd recommend YYYY/MM/DD as a less ambiguous format, but I don't have a perfect answer.

kriops 5 days ago | parent | next [-]

Furthermore, require dashes over slashes to signal that you are expecting ISO-8601 compatible dates, i.e., YYYY-MM-DD. Most users does not know the standard even exists, but it serves as an affordance that it is different from dd/mm/yyyy, etc.

PaulHoule 5 days ago | parent [-]

Those ISO-8601 dates are great for date processing with primitive tools such as GNU sort or awk since they sort lexically, at least if you're not comparing dates in different time zones.

titzer 5 days ago | parent [-]

ISO 8601 is the way.

homebrewer 5 days ago | parent | next [-]

You must mean RFC 3339.

https://ijmacd.github.io/rfc3339-iso8601/

bobmcnamara 5 days ago | parent [-]

Out blasphemous temporal demon! Out!

tshaddox 4 days ago | parent | prev | next [-]

Yes, except for all the completely unhinged stuff in ISO 8601. You probably do not want to deal with durations, or repeating intervals, or even week dates and ordinal dates.

cyanydeez 5 days ago | parent | prev [-]

alas, the user doesnt know the way...

PaulHoule 5 days ago | parent [-]

That part is easy. Use a date picker of some kind

https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/...

or a text field with some code that converts vernacular dates to a structured format. I don't think users are going to be too weirded out at seeing "1997-04-15" and will probably learn to use that natively.

The hard part is that a lot of devs aren't aware that there's a standard and that standard is superior to the alternatives.

senfiaj 5 days ago | parent | prev | next [-]

Every time I see an input date string in XX/XX/YYYY format I get a micro PTSD flashback. This cannot be parsed reliably and is locale dependent. The standard date format is YYYY-MM-DD (it's also the date part of the ISO time format). Raw text inputs should be avoided as much as possible, date/time pickers should be preferred.

hanche 5 days ago | parent | next [-]

Even worse with just two digits for the year! 01/02/03 could be 1 Feb 2003, or 2 Jan 2003, or 3 Feb 2001. Let’s just be thankful no one ever uses any of remaining three permutations.

oneshtein 4 days ago | parent [-]

  $ LANG=C date --date="01/02/03"
  Thu Jan  2 00:00:00 EET 2003

  $ LANG=de_AT.utf8 date --date="01/02/03"
  Do 02 Jän 2003 00:00:00 EET

  $ LANG=zh_HK.utf8 date --date="01/02/03"
  2003年01月02日 星期四 00:00:00 EET
1718627440 2 days ago | parent [-]

That only shows that date only converts on output and not on input.

dragonwriter 5 days ago | parent | prev [-]

> The standard date format is YYYY-MM-DD (it's also the date part of the ISO time format)

Strictly, it is the extended form of the ISO 8601 calendar date format. (The basic format has no separators.)

ISO 8601 allows any of its date formats (calendar date, week date, or ordinal date) to be combined with a time representation for a combined date/time representation, it is inaccurate both to call any of the date formats part of the time format, and to call the calendar date format the format that is part of the combined date/time format.

(There's a reason why people who want to refer to a simple and consistent standard tend to choose RFC-3339 over ISO 8601.)

happytoexplain 5 days ago | parent | prev | next [-]

This isn't really relevant to the parent's topic though, aside from UX. The UI can tell the user which is the day and which is the month. The logic layer knows the format explicitly.

jack_pp 5 days ago | parent | prev [-]

Pretty sure they force the user into that format so that shouldn't be an issue

PaulHoule 5 days ago | parent | prev | next [-]

I guess in your case you're never doing date arithmetic or greater than or less than, but only doing equality testing, right? That is, it's part of a composite key.

I faced a similar problem with a form where people were supposed to submit a date and probably not aware of what timezone was involved. I figured that so long as they selected "02/28/1993" and people always saw "02/28/1993" that was correct and if they ever saw it differently it was wrong. So I used non-TZ aware dates throughout the whole system.

BurningFrog 5 days ago | parent | prev | next [-]

Normal/"legal" dates are not timestamps or related to timezones, and this fact will eternally be rediscovered as long as humans write software.

legulere 4 days ago | parent | prev | next [-]

If you store it just as a string it means that you cannot do anything useful with it like age-dependent logic or you just pass on parsing logic to users of the field.

FHIR in my opinion has a pretty good system for dates (including birthdates): YYYY, YYYY-MM, or YYYY-MM-DD. (Not knowing your exact birthday is common for some countries).

https://build.fhir.org/datatypes.html#date

dragonwriter 5 days ago | parent | prev | next [-]

What environment are you in where you have to work with birthdates, you have timezone aware dates, times, and intervals, but you don't have a naive/plain/local date type that already exists forcing you to use strings in place of date-without-timezone?

You seem to have a reasonably expedient solution for that problem, but it is surprising to have the combination of things you have to have and things you have to be missing to have that problem in the first place.

happytoexplain 4 days ago | parent | next [-]

They may have something that's just not as easy to work with as strings. E.g. in Swift, you have DateComponents, but that's too dynamic (and Date is sometimes referred to as naive, but that's a misunderstanding, since they are timestamps, not date+time).

QuadmasterXLII 3 days ago | parent | prev [-]

Javascript/swift/kotlin frontend, python backend, mysql server. The problem wasn’t that I had access to zero (presumably) well implemented time abstractions with all the bells and whistles, it was that I had five well implemented native time abstractions.

shadowgovt 4 days ago | parent | prev | next [-]

IIUC why medical cares at all, this is really insightful. Because as far as I'm aware, the medical industry basically uses birthdate as a key; it helps to (a) tell two patients with other primary keys (like name or address) apart and (b) do a quick mental-acuity check on the patient by just having them regurgitate the value and doing a human-brain string= on it.

1718627440 2 days ago | parent [-]

The dose varies per age, but this isn't that exact.

kccqzy 5 days ago | parent | prev | next [-]

> people’s birthdays changing when they move timezones

That's because the developers use datetimes (aka timestamps) to store a single date. Just pick an arbitrary epoch date (such as January 1, 1900 as used by Excel, or my favorite January 1, 1600 since 1600 is a multiple of 400 making leap year calculations even simpler) and store the number of days elapsed since then. The rules involving leap years are much much simpler than rules involving timezones and timezone databases. The translation from/to this representation to a broken-down y/m/d takes only ~50 lines of code anyways.

Of course if you don't need to do arithmetic on dates, just store three numbers, year, month, and day.

SoftTalker 5 days ago | parent | next [-]

No, don't do that. Use a date datatype (not date/time). You aren't the first person to ever need to handle dates without times/timezones in a computer program. Use what your database/language/libraries already have to support that.

tadfisher 5 days ago | parent | next [-]

Specifically, a "local date", codified as LocalDate in every date library worth a damn, except for Javascript which chose "PlainDate" just to be different.

PaulHoule 5 days ago | parent | prev [-]

Well, for hardcore chronology Julian dates are what you do.

https://en.wikipedia.org/wiki/Julian_day

which are the moral equivalent of Unix timestamps with a different offset and multiplier. These work OK for human history but will break if you go far enough into the past or the future because uncertainty in the earth's rotation adds up over time.

If you don't care about timezones timezones may still care about you, if you want to minimize trouble it makes sense to properly use timezone-aware Zulu (GMT) dates for everything if you can.

In certain cases you might be doing data analysis or building an operational database for throttling access to an API or something and you know there are 16-bits worth of days, hours, 5-minute periods or something it can make sense to work relative to your own epoch.

5 days ago | parent [-]
[deleted]
happytoexplain 5 days ago | parent | prev | next [-]

In my humble opinion, this is not good advice unless you demonstrably need it for query performance or something. It is very easy for the logic layer to accidentally mess that up, either in reading or, worse, in writing back.

In this case, I'd suggest storing what you mean (the user wasn't born 9,487 days after Jan 1 1970. They were born Dec 23, 1995.)

Storing the literal units (and ONLY the relevant units), as the parent has, is robust and logically+semantically correct (they could add a translation layer for UX so the user doesn't have to be particular, but that's beside the point). Whether you use a string or a struct or some date-only type is moot, as long as you're literally storing the year, month, and day, and only those three things. You can ephemerally convert it to your platform's date type if you need to.

pavel_lishin 5 days ago | parent | prev | next [-]

> The translation from/to this representation to a broken-down y/m/d takes only ~50 lines of code anyways.

Didn't the article explicitly tell us not to write our own date parsing library?

kccqzy 4 days ago | parent [-]

I disagree with that. And furthermore it's not parsing. It's converting between a single integer and a tuple of three integers.

habibur 5 days ago | parent | prev [-]

> or my favorite January 1, 1600 since 1600 is a multiple of 400

You need to deal with 1600 and 2000 being leap year.

While 1700, 1800, 1900 not being a leap year.

I limit dates from 1900 to 2100. All !year%4 = leap year.

Especially when you try to convert int_date to y,m,d things get tricky.

kccqzy 4 days ago | parent | next [-]

That's exactly why I propose a multiple of 400, not a multiple of 100. The proleptic Gregorian cycle is a 400-year cycle. There are 97 leap years in it. What's tricky about it? Just take a look at my code: https://github.com/kccqzy/smartcal/blob/9cfddf7e85c2c65aa6de...

oneshtein 4 days ago | parent [-]

Why not just make a map of dates to days since 0001-01-01 in a plain text, then compress it at build time? We are not constrained by memory anymore.

Just use simple database as source of truth with all days passed since a start of human history (e.g. 6000 years ago) with labels such as "this day 12345678 was known as day XXXX-xx-xx in those regions, also known as YYYY-yy-yy in those regions, also known as ZZZZZ in this specific region". It's not a hard task to automatically compress such database into a compact representation.

jerf 5 days ago | parent | prev [-]

"Years are not leap years, unless % 4, unless % 100, unless % 400."

It's a wacky rule for sure.

2000 was fun. Everyone knows about "unless % 4", but there was also an interesting and very vocal set of people who knew about the "unless % 100" but somehow knew that without knowing about the "unless % 400" part. A very specific level of knowledge.

dlachausse 5 days ago | parent | prev [-]

Even better, just make the user input dates using a calendar date picker widget instead of a text field. This gives you full control of the input.

mattkrause 5 days ago | parent | next [-]

I always find that a little annoying: it takes way too many clicks (esp. for a birth year) and then you've got to find the day of the week.

I'd hate it less if typing updated the widget.

Tyr42 5 days ago | parent | prev [-]

I've had to click back once per year old I am sometimes. I'd rather just type my birthday.