Do not use 'week year': YYYY
github.com
github.com
I would take `format(year(), '/', month(), '/', day())` over ad-hoc format strings by various APIs.
Reading the docs further this also stands out:
> For parsing with the abbreviated year pattern ("y" or "yy"), SimpleDateFormat must interpret the abbreviated year relative to some century. It does this by adjusting dates to be within 80 years before and 20 years after the time the SimpleDateFormat instance is created. For example, using a pattern of "MM/dd/yy" and a SimpleDateFormat instance created on Jan 1, 1997, the string "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64" would be interpreted as May 4, 1964. During parsing, only strings consisting of exactly two digits, as defined by Character.isDigit(char), will be parsed into the default century. Any other numeric string, such as a one digit string, a three or more digit string, or a two digit string that isn't all digits (for example, "-1"), is interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
I hope nobody uses this to parse historical data that happens to become older than 80 years recently.
with your example:
> format_description!("[year]/[month]/[day]")
0: https://crates.io/crates/time
1: https://time-rs.github.io/book/api/format-description.html
I don’t think the language has anything suitable for these purposes. What did you have in mind?
That components have parameters makes the non-literal approach even less compelling: take this which can produce the likes of “2:34:56pm”:
format_description!("[hour padding:none repr:12]:[minute]:[second][period case:lower]")
For reference, that is equivalent to this: use time::format_description::{FormatItem, component::Component, modifier::{Hour, Minute, Second, Period, Padding}};
[
FormatItem::Component(Component::Hour(Hour { padding: Padding::None, is_12_hour_clock: false })),
FormatItem::Literal(b":"),
FormatItem::Component(Component::Minute(Minute { padding: Padding::Zero })),
FormatItem::Literal(b":"),
FormatItem::Component(Component::Second(Second { padding: Padding::Zero })),
FormatItem::Component(Component::Period(Period { is_uppercase: false, case_sensitive: true })),
]
You could easily provide a prettier DSL so that you could write something like this: use time::format_description::shorthand::{HOUR, MINUTE, SECOND, PERIOD, literal};
[
HOUR.padding_none().repr_12(),
literal(":"),
MINUTE,
":".into(), // even this if you wanted
SECOND,
PERIOD.case_lower(),
]
This wouldn’t be awful in the absence of the parse method, but really, once you have that, the format_description macro is just what you want: compact, checked at compile time, and matching a runtime equivalent which can take user-provided format strings.(Now there are two or three changes I’d prefer to make to format_description’s syntax: I’d use = instead of :, the two being generally very similar but : far more regularly occurring in literal parts, so that the different = would make it scan better; and I think that escaping opening square brackets by doubling them but not requiring doubling for closing square brackets was a particularly bad idea; and I’m mildly inclined to prefer {} to []. So I might end up with "{hour padding=none repr=12}:{minute}:{second}{period case=lower}".)
Yes, but it would not have helped with the bug in question. The parallel bug for a strongly typed interface would be "year()" returning this monstrosity, while "iso_year()" or some other poorly named variant returning the expected year.
No API is immune to footguns and bad design decisions.
On the other hand the much less often used "year of week" would be named "year_of_week()" and hence it is clear to everyone that's not likely what you want.
That would be the sane thing to do. But the same applies to `YYYY`: it should be used for "year of era". But it hasn't been and that's the problem here. For contrast in moment.js, YYYY and yyyy do what you expect and "week year" is GGGG or gggg.
Why? `yyyy` is simpler to type, so makes a lot more sense for "year of era".
> For contrast in moment.js, YYYY and yyyy do what you expect and "week year" is GGGG or gggg.
In LDML (which I assume is what SimpleDateFormat uses), the G field is already spoken for the era name (BC/BCE and AD/CE).
var date = new Date()
date.getYear() // 122
I plugged this into another object and couldn't understand why it was setting the date to 0122. Like, what significance does 122 years from 1900 even have?Turns out I had to use getFullYear, which is of course perfectly intuitive.
Even when javascript was written (and likewise with things like perl and php - and of course the underlying function dated back to the 70s ), it was still quite common for people to write dates by hand as 6/6/96 for June 6th 1996, hence people would often write something like
print day() + "/" + mon() + "/" + year()
If you wanted to put a 4 digit year many people
print day() + "/" + mon() + "/19" + year()
After 2000 came round, many sites would claim the year was 19100, I still saw these sites until well into the late 00s
However some people wrote print day() + "/" + mon() + "/" + (1900 + year())
Which would give the correct display.
Changing year() from returning a 2 digit (which in the 80s and even in the 90s was often preferable) to 4 digits would be a breaking change. Changing it to being "the last two digits" rather than "number of years since 1900" would be a breaking change. That's not something that people like to do, hence things like "getFullYear" to return a 4 digit year (or 3 digit for <1000 etc)
This kind of feature[1] lead to many potential Y2K bugs.
[1] Which it shares with Perl and probably many other languages.
date.getYear() + "-" + date.getMonth() + "-" + date.getDay()
in the hope of getting something like "2022-1-4".The actual result for that date is "122-0-2" - not a single component of the date was what I expected.
I'm sure you've read the docs since then but just for the viewers at home:
s/getYear/getFullYear
s/getDay/getDate
getYear is oldfashioned, years since 1900. getDay is day-of-the-week, 0 for Sunday, 1 for Monday, etc
Why, if everything else in JS is 0 indexed? Isn't consistency a good thing in a language? If they'd gone the other way, wouldn't there be people here complaining that 1 indexing is whack?
99: 1999
100: 2000
…
122: 2022No, the parallel for this would be called `week_year()`, or `iso_week_year()`, to be paired with an `iso_week()` (or `year_week()` since there really is no other formal week-year).
Imagine an API which had methods 'getYear()' and 'GET_YEAR()' where the latter returns the payroll week year, while the former returns the, you know, actual year.
That's what having YYYY produce the week year feels like - a blatant violation of the principle of least surprise.
Which is why your op was saying this is like having an API where year() returns a surprising number thta isn't the year.
You know, like JavaScript does.
There's the part that the entire LDML datetime grammar works like that.
Literally the next letter in your pattern will make it clear: M is the month, and m is the minute.
Which, granted, makes "y" designating the calendar year less than ideal as you'd want to pair Y with M, rather than y with M. Even more so as the 24-hour hour is H, so your standard ISO-8601 pattern is yyyy-MM-ddTHH:mm:ssZ which is... a bit of a mess casing-wise.
> Imagine an API which had methods 'getYear()' and 'GET_YEAR()' where the latter returns the payroll week year, while the former returns the, you know, actual year.
A big difference is the LDML pattern-space is rather more limited, and casing is definitely an important component of it.
> Which is why your op was saying this is like having an API where year() returns a surprising number thta isn't the year.
> You know, like JavaScript does.
If you want to rag on JS's datetime API, which really is Java's, first get in line, second getYear is hardly the worst offender (that belongs to the paired glue-eaters that are getDay and getDate).
But there's a little bit of a difference between mm/MM being minutes/months, where at least as someone thinking about datetime formatting you likely have a concept that the concepts of minutes and months are plausible things 'm' might stand for, vs. yyyy/YYYY where unless you've come across it before, the idea that there might be an inbuilt concept of a 'week year' is likely not going to be something that occurs to you.
I suppose the fact that both 'yyyy/mm/dd' and 'YYYY/MM/DD' produce values that are obviously wrong most of the time should be enough of an incentive for developers to go and look. up the codes. But that said, 'YYYY/MM/DD' produces what looks like the right answer for most of January...
When I write code that needs to format in different formats I always create well named, perhaps verbose but I don't care as it's now readable, functions such as this (ignore HN butchering the code):
/\*
\* Formats Date into "twelve hour time". For example, 3:23. This is "h:mm" format from date-fns.
\* @param { Date } date The date.
\* @returns { string } The formatted string.
\*/
export const formatAsTwelveHourTime = (date: Date) => format(date, 'h:mm');Verbose naming, yes, on the other hand would probably have made the situation clearer.
If you use java, consider using google error-prone. It has a check for that (and many other things) https://errorprone.info/bugpattern/MisusedWeekYear
The last few days of 2021 logged as 2022 makes sense for payroll, but how the heck is Jan 1st 2022 put in the 2023 year?
get date as (week year, month, day)
add start_interval to date using regular time handling functions
print "task scheduled to start on: ", date
When you run this code on 31 December 2021 and schedule the task to run 1 day later, you will see 2023 in the logsThis only happened shortly after midnight on January 1st. So I guess this is somehow related to different time zones/offsets.
2022-12-31 23:16:54.013
And then our UI converted this entry into one with 2023
(it correctly assumes that the string is in UTC and then converted it to local time +1h)
A linter should probably flag any format that mixes "week year" with month and days. It's really for use by itself or with payroll week number.
First off, I think you have a typo(?) with the "2023"? Because:
* https://www.epochconverter.com/weeks/2022
The reason(s) why the week system was designed:
> * All weeks have exactly 7 days, i.e. there are no fractional weeks.
> * Every week belongs to a single year, i.e. there are no ambiguous or double-assigned weeks.
> * The date directly tells the weekday.
> * All week-numbering years start with a Monday and end with a Sunday.
> * When used by itself without using the concept of month, all week-numbering years are the same except that some years have a week 53 at the end.
* https://en.wikipedia.org/wiki/ISO_week_date#Advantages
This makes perfect sense if your role is in bookkeeping / accounting / finance. Your life is based on pay periods and not on day-of-year/month. A particular pay day "belonging" to a particular year is a meaningless affectation: people need to get paid every two weeks come hell or high water, and anything that can complicate the smooth running of that process is discarded.
As for the algorithm:
> The ISO 8601 definition for week 01 is the week with the first Thursday of the Gregorian year (i.e. of January) in it. The following definitions based on properties of this week are mutually equivalent, since the ISO week starts with Monday: […]
It's not just payrolls, it's also things like deliveries or synchronisation. And saying "monday week 15" (or writing "W15 mon") aside from being short and precise can give a better ground for synchronisation or collision than "April 11th" e.g. the person you're talking to might know that every monday would be an issue and be able to say that that's not an option before even looking it up.
It's not, it's a fiscal, or more generally business creation, allowing businesses to express everything in weekly basis, with no variance (all weeks are 7 days mon-sun with no fractionals, and every week belongs to a single year with no ambiguity).
This is very, very common usage in european businesses, and even casual settings, to the extent that you'll have a hard time finding a physical calendar without weeks printed on it.
edit: this wouldn't work, since W is reserved for weeks in month
Format specifiers shouldn’t be case sensitive. Better mnemonics would be even better.
> The reference time used in these layouts is the specific time stamp: 01/02 03:04:05PM '06 -0700 (January 2, 15:04:05, 2006, in time zone seven hours west of GMT). That value is recorded as the constant named Layout, listed below. As a Unix time, this is 1136239445.
> RFC3339 = "2006-01-02T15:04:05Z07:00"
You know, assuming the earth's frame of reference....
Display timestamps however the user wants.
Do not roll your own timestamp code. See "Falsehoods programmers believe about time".