Incorrect date formatting - mysql

I am having problems with date formats, was trying many formatting solutions but non of them was working.
I do have table with dates and I am summing repeating dates:
SELECT
tt.time,
DATE_FORMAT(tt.time, '%x-%v') AS time_label,
SUM(value) AS value
FROM
time_table tt
GROUP BY DATE_FORMAT(tt.time, '%x-%v')
ORDER BY time ASC
As we can see it's formatting end year date as a new year date. w3school %x is saying
Year for the week where Monday is the first day of the week. Used with %V
and for %v
Week where Monday is the first day of the week (01 to 53). Used with %X

The first week of the year 2020 started on Dec 30, 2019. See Week Numbers for 2020.
Therefore 2019-12-30 12:42:53 is being formatted correctly as 2020-01.

Related

Is there any function in sql to extract week?

I want to extract week from datetime, the output I want is 'YY/week', where week is the week of the year (eg '201724' is the 24th week in 2017).
The term "week of the year" is too ambiguous.
The week may start from Sunday, Monday or another weekday
The weeks enumeration in the year may start from 0 or 1
The weeks enumeration in the year may start from the week which includes January, 1 (and hence may be partial) or from first complete week of the year
The last week of the year, if it is partial, may be counted or not
Each DBMS has its own functions (sometimes original, always with original names) that can return the number of the week in the year on a given date. But they can not always take into account the above features.
Important addition provided by jarlh:
ISO 8601 (#4.3.4):
The first calendar week of a year is the one that includes the first Thursday of that year.
The last calendar week of a calendar year is the week immediately preceding the first calendar week of the next calendar year.
Week 1 is the first week of a year.
A calendar week starts on a Monday.
ISO 9075 doesn't even mention weeks.
SELECT TO_CHAR(TO_DATE('19-FEB-22') , 'IW') from DUAL;
To get the corresponding four-digit year, use
SELECT TO_CHAR(TO_DATE('19-FEB-22'), 'IYYY') FROM DUAL;
TO_CHAR() having so many options like this read more in Oracle manual or extract portation of date Extract Portion of Date Time Value
OutPut
You can use the following Mysql type query to extract.
SELECT DATE_FORMAT(BirthDate, " %u %Y") FROM Employees;
where the BirthDate date column in the database and the Employees is the table name.
This will result
49 1968
08 1952
35 1963
Week and the year.
in postgresql:
SELECT to_char('2016-12-31 13:30:15'::timestamp without time zone, 'yy/ww') ;
result:
16/53

mysql date_format() - what is the difference between %U and %V flags?

I saw in the documentation of mysql date_format() that either it returns 53 or 54 different values. As far as I know years have either 52 or 53 weeks. Where is this extra week coming from?
%U Week where Sunday is the first day of the week (00 to 53)
%u Week where Monday is the first day of the week (00 to 53)
%V Week where Sunday is the first day of the week (01 to 53). Used with %X
%v Week where Monday is the first day of the week (01 to 53). Used with %X
https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html
What is the difference between U and V flags then? Shouldn't U flags have a range of (00 to 52)?
Thanks!
From what you can see when playing with dates from range 2018-01-01 (Monday) - 2018-01-07 (Sunday) you can see:
that Vs can return previous year week number:
SELECT DATE_FORMAT("2018-01-05", "%V");
return 53 (here week starts with Sunday, which is in 2017). %v works the same, but for above would return 0, as Monday is the first day of the week and is already in 2018.
Us don't have that property: for the above with %U would return 1.

convert Mysql week to Date using Date.commercial

After Fetching objects form database
Object.select('week(created_at) as week, year(created_at) as year')
which returns week from 0 to 53 then creating a Date object from it
Date.commercial(x.year,x.week,1)
It reporting 'invalid date' error due to 0 and 53 no of week.
I have also tried this one.
Date.strptime("#{x.year}-#{x.week+1}-1","%Y-%W-%w")
But it also crashing due to x.week+1 (e.g. 53+1). Searching one line solution
I think your usage is wrong
I found this below from (here).
To be sure, please send your ruby version and also your x.year and x.week output.
commercial(y=-4712, w=1, d=1, sg=ITALY) click to toggle source
Create a new Date object for the Commercial Date specified by year y, week-of-year w, and day-of-week d.
Monday is day-of-week 1; Sunday is day-of-week 7.
w and d can be negative, in which case they count backwards from the end of the year and the end of the week respectively. No wraparound is performed, however, and invalid values cause an ArgumentError to be raised.
y defaults to -4712, w to 1, and d to 1; this is Julian Day Number day 0.
sg specifies the Day of Calendar Reform.emphasized text
Don't add 1 to x.week in your strptime.
You should fetch the date from DB in an ISO 8601 format.
This format returns weeks from 1 to 52, that complies with the input that #commercial expects.
In Ruby:
ISO 8601 week-based year and week number:
The week 1 of YYYY starts with a Monday and includes YYYY-01-04.
The days in the year before the first week are in the last week of
the previous year.
%G - The week-based year
%g - The last 2 digits of the week-based year (00..99)
%V - Week number of the week-based year (01..53)
from https://ruby-doc.org/stdlib-2.5.1/libdoc/date/rdoc/Date.html#method-i-strftime
In MySQL:
%V Week (01..53), where Sunday is the first day of the week; WEEK() mode 2; used with %X
%v Week (01..53), where Monday is the first day of the week; WEEK() mode 3; used with %x
%X Year for the week where Sunday is the first day of the week, numeric, four digits; used with %V
%x Year for the week, where Monday is the first day of the week, numeric, four digits; used with %v
SELECT DATE_FORMAT('1999-01-01', '%X %V');
+------------------------------------+
| DATE_FORMAT('1999-01-01', '%X %V') |
+------------------------------------+
| 1998 52 |
+------------------------------------+
from https://dev.mysql.com/doc/refman/5.6/en/date-and-time-functions.html#function_date-format

Week of the year for weeks starting with Saturday

We have customers that currently have defined weeks starting either on Sat, Sun or Monday. Came across these DATE_FORMAT options which nicely handle the week starting on Sunday and Monday but can't find a way to do same for week starting on Saturday. Any suggestions?
%U Week (00..53), where Sunday is the first day of the week
%u Week (00..53), where Monday is the first day of the week
I had a similar issue: I needed to calculate week numbers based on the following rules:
Week starts on Friday
The remainder days of an year (all the days after the last Friday of the year that do not complete a week) should be counted in the
first week of the next year.
For example:
27/12/2012 (Thursday) should be Week 52 of 2012
28/12/2012 (Friday) should be Week 1 of 2013
Week 1 2013 goes from 28/12/2012 to 3/1/2013
I made this statement that calculates both the YEAR and WEEKNUMBER based on these rules that you can easily adapt to your circunstance:
SELECT IF(ceil(( dayofyear(current_date) + dayofweek(date_format(current_date, '%Y-01-01'))+1 )/7) > 52, YEAR(current_date)+1, YEAR(current_date)),
IF(ceil(( dayofyear(current_date) + dayofweek(date_format(current_date, '%Y-01-01'))+1 )/7) > 52, 1, ceil(( dayofyear(current_date) + dayofweek(date_format(current_date, '%Y-01-01'))+1 )/7));
The tricky part is just this expression:
ceil(( dayofyear(current_date) + dayofweek(date_format(current_date, '%Y-01-01'))+1 )/7)
The rest (If clauses) are just for adapting the result of the expression to make year+1 and week = 1 on week 53.
I'll try to explain the expression as best as I can. The following expression gives you the week number pure simple (the day of the year divided by 7 days in a week rounded up):
ceil(( dayofyear(current_date))/7)
But now you want to make it start on Friday (or any other day). To do this you need to add to the current day, the days of the first week that were part of the previous year (it's like your current actually started a few days before, because your first week contains days from the previous year).
This expression calculates that offset based on the weekday on Jan/1:
dayofweek(date_format(current_date, '%Y-01-01'))+OFFSET
The offset is the difference between 7 and the weekdaynumber you want the week to start:
0 for Saturday
1 for Friday
2 for Thursday
3 for Wednesday
...
So now you just have to add it to the previous one resulting in the above mentioned expression that calculates the week numbers starting on any weekday and assuming week 1 to start on the previous year:
ceil(( dayofyear(current_date) + dayofweek(date_format(current_date, '%Y-01-01'))+OFFSET )/7)
Then I just added an IF that turns week 53 into week 1 and another to add 1 to the year if it's week 53.
It took me a while to think on this question.
ISO standard defines the first week to start on Monday and contain 4th day of the year.
MySQL's functions provide much more choices.
date_format() flags %U and %u are using notation where first week is the one where Sunday or Monday is first met. As this is not according to the ISO, I will provide both variants.
If you want to count week numbers starting from Saturday and first year's week is the one containing Saturday, you can use one of the following expressions:
SELECT sign(dayofweek(current_date) - 7) + ceil(dayofyear(current_date)/7);
SELECT ceil((dayofyear(current_date)+
(dayofweek(date_format(current_date, '%Y-01-01'))%7-7))/7);
If first year's week is the one where 4th day of the year falls into, use:
SELECT ceil((dayofyear(current_date)+
(dayofweek(date_format(current_date, '%Y-01-04'))%7-4+1))/7);
The very first expression is quite straightforward.
I will elaborate on the 2nd and 3rd ones. I calculate week number by taking current day of the year, dividing by 7 and ceiling up, quite simple. Week number needs to be adjusted based on the situation at the beginning of the year though.
for the first case (first week starts with the first Saturday), I take day-of-week for the Jan/1 of the year in subject, make Saturday as the day 0 and then adjust day-of-year by the difference. This this makes all days before first saturday yielding negative adjustment number and it ceils up to zero;
for the second case (first week is the one where 4 day of the year falls in), I take day-of-week for the Jan/4 of the year in subject, make Saturday as the day 0. The -4+1 formula gives adjustment to the first Saturday before Jan/4, +1 is used as days of the year starts from 1, not from 0. Negative adjustment means 1st day of the year is not in the first week of the year.
Here're some test dates on the SQL Fiddle.
If you want to count weeks from any other day, you just have to change the formula, making that day being 0 in the sequence. Say, to count weeks starting from Wednesday, use:
SELECT ceil((dayofyear(current_date)+
((dayofweek(date_format(current_date, '%Y-01-04'))+3)%7-4+1))/7);
+3 is used as it complements dayofweek() value for Wednesday to the 7.
Make an adjustment based on DAYOFWEEK().

How to group by week in MySQL?

Oracle's table server offers a built-in function, TRUNC(timestamp,'DY'). This function converts any timestamp to midnight on the previous Sunday. What's the best way to do this in MySQL?
Oracle also offers TRUNC(timestamp,'MM') to convert a timestamp to midnight on the first day of the month in which it occurs. In MySQL, this one is straightforward:
TIMESTAMP(DATE_FORMAT(timestamp, '%Y-%m-01'))
But this DATE_FORMAT trick won't work for weeks. I'm aware of the WEEK(timestamp) function, but I really don't want week number within the year; this stuff is for multiyear work.
You can use both YEAR(timestamp) and WEEK(timestamp), and use both of the these expressions in the SELECT and the GROUP BY clause.
Not overly elegant, but functional...
And of course you can combine these two date parts in a single expression as well, i.e. something like
SELECT CONCAT(YEAR(timestamp), '/', WEEK(timestamp)), etc...
FROM ...
WHERE ..
GROUP BY CONCAT(YEAR(timestamp), '/', WEEK(timestamp))
Edit: As Martin points out you can also use the YEARWEEK(mysqldatefield) function, although its output is not as eye friendly as the longer formula above.
Edit 2 [3 1/2 years later!]:
YEARWEEK(mysqldatefield) with the optional second argument (mode) set to either 0 or 2 is probably the best way to aggregate by complete weeks (i.e. including for weeks which straddle over January 1st), if that is what is desired. The YEAR() / WEEK() approach initially proposed in this answer has the effect of splitting the aggregated data for such "straddling" weeks in two: one with the former year, one with the new year.
A clean-cut every year, at the cost of having up to two partial weeks, one at either end, is often desired in accounting etc. and for that the YEAR() / WEEK() approach is better.
Figured it out... it's a little cumbersome, but here it is.
FROM_DAYS(TO_DAYS(TIMESTAMP) -MOD(TO_DAYS(TIMESTAMP) -1, 7))
And, if your business rules say your weeks start on Mondays, change the -1 to -2.
Edit
Years have gone by and I've finally gotten around to writing this up.
https://www.plumislandmedia.net/mysql/sql-reporting-time-intervals/
The accepted answer above did not work for me, because it ordered the weeks by alphabetical order, not chronological order:
2012/1
2012/10
2012/11
...
2012/19
2012/2
Here's my solution to count and group by week:
SELECT CONCAT(YEAR(date), '/', WEEK(date)) AS week_name,
YEAR(date), WEEK(date), COUNT(*)
FROM column_name
GROUP BY week_name
ORDER BY YEAR(DATE) ASC, WEEK(date) ASC
Generates:
YEAR/WEEK YEAR WEEK COUNT
2011/51 2011 51 15
2011/52 2011 52 14
2012/1 2012 1 20
2012/2 2012 2 14
2012/3 2012 3 19
2012/4 2012 4 19
You can get the concatenated year and week number (200945) using the YEARWEEK() function. If I understand your goal correctly, that should enable you to group your multi-year data.
If you need the actual timestamp for the start of the week, it's less nice:
DATE_SUB( field, INTERVAL DAYOFWEEK( field ) - 1 DAY )
For monthly ordering, you might consider the LAST_DAY() function - sort would be by last day of the month, but that should be equivalent to sorting by first day of the month ... shouldn't it?
Just ad this in the select :
DATE_FORMAT($yourDate, \'%X %V\') as week
And
group_by(week);
If you need the "week ending" date this will work as well. This will count the number of records for each week. Example: If three work orders were created between (inclusive) 1/2/2010 and 1/8/2010 and 5 were created between (inclusive) 1/9/2010 and 1/16/2010 this would return:
3 1/8/2010
5 1/16/2010
I had to use the extra DATE() function to truncate my datetime field.
SELECT COUNT(*), DATE_ADD( DATE(wo.date_created), INTERVAL (7 - DAYOFWEEK( wo.date_created )) DAY) week_ending
FROM work_order wo
GROUP BY week_ending;
Previous Sunday:
STR_TO_DATE(CONCAT(YEARWEEK(timestamp,2),'0'),'%X%V%w')
Previous Monday:
STR_TO_DATE(CONCAT(YEARWEEK(timestamp,3),'1'),'%x%v%w')
DATE_FORMAT(date,format) reference:
%V - Week (01..53), where Sunday is the first day of the week; WEEK() mode 2; used with %X
%v - Week (01..53), where Monday is the first day of the week; WEEK() mode 3; used with %x
%w - Day of the week (0=Sunday..6=Saturday)
%X - Year for the week where Sunday is the first day of the week, numeric, four digits; used with %V
%x - Year for the week, where Monday is the first day of the week, numeric, four digits; used with %v
I like the week function in MySQL, but in my situation, I wanted to know which week of the month a row was in. I utlized this solution:
where run_date is a timestamp like 2021-02-25 00:00:00
concat (
date_format(run_date, '%Y-%m'),
' wk ',
(week(run_date,1) - ( week(date_format(run_date, '%Y-%m-01')) - 1))
) as formatted_date
This outputs:
2021-02-23 ---> 2021-02 wk 4
2021-02-25 ---> 2021-02 wk 4
2021-02-11 ---> 2021-02 wk 2
2021-03-02 ---> 2021-03 wk 1
The idea behind this is that I want to know (with relative certainty) which week of the month in question did the date occur?
So we concatenate:
date_format(run_date, '%Y-%m') to get 2021-02
then we add the literal text string wk
then we use:
week(run_date, 1) to get the week (1 to start Monday) of this record, (which would be 7 because 02/21/2021 is in the 7th week of the year, and we subtract whatever the week is on the 1st day of this same month - the week() for 2021-02-01 is 5, because it is in the 5th week of the year:
(week(date_format(run_date, '%Y-%m-01'))
Unfortunately, this will start out the counting at 0, which people don't like, so we subtract 1 from the last part of the concatenation result so that the "week" start at 1.
This may be a good option:
SELECT
year(datetime_field) as year_date, week(datetime_field) as week_date
FROM
bd.table
GROUP BY
year_date, week_date;
It would look like this:
'2020', '14'
'2020', '15'
'2020', '16'
'2020', '17'
'2020', '18'