05AB1E, 130 128 133 131 124 123 bytes
žežfžg)V0[Y`UÐ3‹12*+>13*5÷s3‹Xα©т%D4÷®т÷©4÷®·()ćsO7%2@+Y`т‰0Kθ4ÖUD2Qi\28X+ë<7%É31α}‹iY¬>0ëY1¾ǝDÅsD12‹i>1ë\1Dǝ¤>2}}ǝVYI'.¡Q#
I'm out of my mind..
For the golfing language 05AB1E it doesn't matter at all whether the input is with .
or -
. However, 05AB1E doesn't have any builtins for Date objects or calculations. The only builtin regarding dates it has is today's year/month/day/hours/minutes/seconds/microseconds.
So because of that, almost all of the code you see are manual calculations to go to the next day, and calculating the day of the week.
+5 bytes due to a part I forgot in Zeller's formula (year-1 for months January and February)..
Try it online or Try it online with an emulated self-specified date of 'today'.
Explanation:
Wall of text incoming.
In general, the code follows the following pseudo-code:
1 Date currentDate = today;
2 Integer counter = 0;
3 Start an infinite loop:
4* If(currentDate is NOT a Saturday and currentDate is NOT a Sunday):
5 Counter += 1;
6* currentDate += 1; // Set currentDate to the next day in line
7 If(currentDate == parsed input-string):
8 Stop the infinite loop, and output the counter
1) Date currentDate = today;
is this part of the 05AB1E program:
že # Push today's day
žf # Push today's month
žg # Push today's year
) # Wrap them into a single list
V # Pop and store this list in variable `Y`
2) Integer counter = 0;
and 3) Start an infinite loop:
are straight-forward in the 05AB1E program:
0 # Push 0 to the stack
[ # Start an infinite loop
4) If(currentDate is NOT a Saturday and currentDate is NOT a Sunday):
is the first hard part with manual calculations. Since 05AB1E has no Date builtins, we'll have to calculate the Day of the Week manually.
The general formula to do this is:
$${\displaystyle h=\left(q+\left\lfloor {\frac {13(m+1)}{5}}\right\rfloor +K+\left\lfloor {\frac {K}{4}}\right\rfloor +\left\lfloor {\frac {J}{4}}\right\rfloor -2J\right){\bmod {7}},}$$
Where for the months March through December:
- \$q\$ is the \$day\$ of the month (
[1, 31]
)
- \$m\$ is the 1-indexed \$month\$ (
[3, 12]
)
- \$K\$ is the year of the century (\$year \bmod 100\$)
- \$J\$ is the 0-indexed century (\$\left\lfloor {\frac {year}{100}}\right\rfloor\$)
And for the months January and February:
- \$q\$ is the \$day\$ of the month (
[1, 31]
)
- \$m\$ is the 1-indexed \$month + 12\$ (
[13, 14]
)
- \$K\$ is the year of the century for the previous year (\$(year - 1) \bmod 100\$)
- \$J\$ is the 0-indexed century for the previous year (\$\left\lfloor {\frac {year-1}{100}}\right\rfloor\$)
Resulting in in the day of the week \$h\$, where 0 = Saturday, 1 = Sunday, ..., 6 = Friday.
Source: Zeller's congruence
We can see this in this part of the 05AB1E program:
Y # Push variable `Y`
` # Push the day, month, and year to the stack
U # Pop and save the year in variable `X`
Ð # Triplicate the month
3‹ # Check if the month is below 3 (Jan. / Feb.),
# resulting in 1 or 0 for truthy/falsey respectively
12* # Multiply this by 12 (either 0 or 12)
+ # And add it to the month
# This first part was to make Jan. / Feb. 13 and 14
> # Month + 1
13* # Multiplied by 13
5÷ # Integer-divided by 5
s3‹ # Check if the month is below 3 again (resulting in 1 / 0)
Xα # Take the absolute difference with the year
© # Store this potentially modified year in the register
т% # mYear modulo-100
D4÷ # mYear modulo-100, integer-divided by 4
®т÷©4÷ # mYear integer-divided by 100, and then integer-divided by 4
®·( # mYear integer-divided by 100, doubled, and then made negative
) # Wrap the entire stack into a list
ć # Extract the head (the counter variable that was also on the stack)
s # Swap so the calculated values above are as list at the top
O # Take the sum of this entire list
7% # And then take modulo-7 to complete the formula,
# resulting in 0 for Saturday, 1 for Sunday, and [2, 6] for [Monday, Friday]
2@ # Check if the day is greater than or equal to 2 (so a working day)
5) Counter += 1;
is straight-forward again:
# The >=2 check with `2@` results in either 1 for truthy and 0 for falsey
+ # So just adding it to the counter variable is enough
6) currentDate += 1; // Set currentDate to the next day in line
is again more complex, because we have to do it manually. So this will be expanded to the following pseudo-code:
a Integer isLeapYear = ...;
b Integer daysInCurrentMonth = currentDate.month == 2 ?
c 28 + isLeapYear
d :
e 31 - (currentDate.month - 1) % 7 % 2;
f If(currentDate.day < daysInCurrentMonth):
g nextDate.day += 1;
h Else:
i nextDate.day = 1;
j If(currentDate.month < 12):
k nextDate.month += 1;
l Else:
m nextDate.month = 1;
n nextDate.year += 1;
Sources:
Algorithm for determining if a year is a leap year. (EDIT: No longer relevant, since I use an alternative method to check leap years which saved 7 bytes.)
Algorithm for determining the number of days in a month.
6a) Integer isLeapYear = ...;
is done like this in the 05AB1E program:
Y # Push variable `Y`
` # Push the days, month and year to the stack
т‰ # Divmod the year by 100
0K # Remove all items "00" (or 0 when the year is below 100)
θ # Pop the list, and leave the last item
4Ö # Check if this number is visible by 4
U # Pop and save the result in variable `X`
Also used in this 05AB1E answer of mine, so there some example years are added to illustrate the steps.
6b) currentDate.month == 2 ?
and 6c) 28 + isLeapYear
are done like this:
D # Duplicate the month that is now the top of the stack
2Q # Check if it's equal to 2
i # And if it is:
\ # Remove the duplicated month from the top of the stack
28X+ # Add 28 and variable `X` (the isLeapYear) together
6d) :
and 6e) 31 - (currentDate.month - 1) % 7 % 2;
are done like this:
ë # Else:
< # Month - 1
7% # Modulo-7
É # Is odd (shortcut for %2)
31 # Push 31
α # Absolute difference between both
} # Close the if-else
6f) If(currentDate.day < daysInCurrentMonth):
is done like this:
‹ # Check if the day that is still on the stack is smaller than the value calculated
i # And if it is:
6g) nextDate.day += 1;
is done like this:
Y # Push variable `Y`
¬ # Push its head, the days (without popping the list `Y`)
> # Day + 1
0 # Push index 0
# (This part is done after the if-else clauses to save bytes)
}} # Close the if-else clauses
ǝ # Insert the day + 1 at index 0 in the list `Y`
V # Pop and store the updated list in variable `Y` again
6h) Else:
and 6i) nextDate.day = 1;
are then done like this:
ë # Else:
Y # Push variable `Y`
1 # Push a 1
¾ # Push index 0
ǝ # Insert 1 at index 0 (days part) in the list `Y`
6j) If(currentDate.month < 12):
:
D # Duplicate the list `Y`
Ås # Pop and push its middle (the month)
D12‹ # Check if the month is below 12
i # And if it is:
6k) nextDate.month += 1;
:
> # Month + 1
1 # Push index 1
# (This part is done after the if-else clauses to save bytes)
}} # Close the if-else clauses
ǝ # Insert the month + 1 at index 1 in the list `Y`
V # Pop and store the updated list in variable `Y` again
6l) Else:
, 6m) nextDate.month = 1;
and 6n) nextDate.year += 1;
are then done like this:
ë # Else:
\ # Delete the top item on the stack (the duplicated month)
1 # Push 1
D # Push index 1 (with a Duplicate)
ǝ # Insert 1 at index 1 (month part) in the list `Y`
¤ # Push its tail, the year (without popping the list `Y`)
> # Year + 1
2 # Index 2
# (This part is done after the if-else clauses to save bytes)
}} # Close the if-else clauses
ǝ # Insert the year + 1 at index 2 in the list `Y`
V # Pop and store the updated list in variable `Y` again
And finally at 8) If(currentDate == parsed input-string):
and 9) Stop the infinite loop, and output the counter
:
Y # Push variable `Y`
I # Push the input
'.¡ '# Split it on dots
Q # Check if the two lists are equal
# # And if they are equal: stop the infinite loop
# (And output the top of the stack (the counter) implicitly)
9
Is there any specific reason behind this "unofficial" European input format? Our consensus is to allow flexible input whenever possible.
– Arnauld – 2018-10-01T11:36:50.3601@Arnauld because most of the date methods I know are in a specific format and I don't know any method which will use the
dd.MM.yyyy
immediately -> it may make the task a bit more challenging. May there are some disadvantages for some languages, but I don't know any language which will profit out of this rule :) – Hille – 2018-10-01T11:39:42.2831I feel like the example should output
4
since until the start of17.12.2018
(Mon) there are only four whole weekdays (i.e. today,10.12.2018
, should not be counted as soon as we've entered it). – Jonathan Allan – 2018-10-01T11:58:42.350@JonathanAllan but I wont to have all days left so if its the last day before the date it should be 1 and not 0 :) – Hille – 2018-10-01T12:15:07.940
@Hille if we run it today at 23:00 with an input of tomorrow do we really have 1 day?! – Jonathan Allan – 2018-10-01T12:29:10.273
So, what should be the output for Sunday / Saturday? And should public holidays be considered? – tsh – 2018-10-01T12:33:27.637
@JonathanAllan, as far as I know, does the 0 marks that it is over / the goal is reached, so I don't think that it makes sense to mark one day left as 0? Or? – Hille – 2018-10-01T13:05:00.573
@tsh as they don't count the end date is set to friday and no public holidays should not be considered as they are different in every country. – Hille – 2018-10-01T13:06:13.500
6Is there really any point in adding the "extra challenge" of a hard to process date format? That just seems unfair w.r.t. languages that have flexible date formats... – Quintec – 2018-10-01T13:25:54.523
1@Quintec it isn't as hard as you say, there are just a few extra bits and bytes for reformatting it :P – Hille – 2018-10-01T13:28:48.703
3@Hille I did not say it was "hard", it just is an unnecessary hassle, especially in code-golf...do note the link that Arnauld posted above... generally flexible input is the norm... – Quintec – 2018-10-01T13:31:26.747
@tsh holidays will depend on locale, so I wouldn't go there. – ngm – 2018-10-01T13:50:36.633
1@Quintec sry but that's the task. I don't want to have any input format so that specific code golf languages got a big advantage :) – Hille – 2018-10-01T13:57:30.850
6
By the way, you note that this is your first challenge; I invite you to use The Sandbox for refinement before posting a challenge to main! Otherwise, nice work, and I'll enjoy seeing some more from you!
– Giuseppe – 2018-10-01T16:49:47.903Can we return
false
instead of0
? – Shaggy – 2018-10-01T18:40:27.6237Not really impressed by the strict input format but other than that a good challenge. – ElPedro – 2018-10-01T19:52:17.117
@Shaggy not the beuautifullest solution but i think its ok :) – Hille – 2018-10-02T05:54:37.243
Thanks, saved me a byte. You should update the challenge to include that in the spec. – Shaggy – 2018-10-02T07:54:21.817
1A lot of people are complaining about the strict input format. I didn't think it was a problem. Sure, 50% of the characters in my solutions are about dealing with the format, but on the other hand, if I could just write
DayCount[Today,#,"Weekdays"]&
then I'd have nothing to golf :) And as the 05AB1E solution shows, dealing with a problem your language doesn't handle for you can be a fun challenge. But the format game is bad if it's done too often - the next time I have a date in the wrong format, I'll do the same thing - so this is something to use sparingly. – Misha Lavrov – 2018-10-03T17:08:06.747Saturday to Sunday is
0
, no doubt. But I guess that Friday -> Saturday and Sunday -> Monday are both1
. I suggest additional examples for clarification. – Titus – 2018-10-04T15:02:45.930Oh and what about Friday to Monday? One workday or two? – Titus – 2018-10-04T16:43:07.693
"I don't know any method which will use the dd.MM.yyyy immediately" ... I know one. ;) Oh and one more question: Do we have to respect DST? – Titus – 2018-10-04T17:10:19.810
@Titus No, you don't need to respect DST and From Friday -> Sa / Su is 0 days because you do not have to work until the date and for Mo, it's 1. – Hille – 2018-10-04T18:49:08.970