News:

Wondering if this will always be free?  See why free is better.

Main Menu

ISO week numbers

Started by andershz, April 12, 2012, 10:15:33 AM

Previous topic - Next topic

andershz

This is in response to http://www.simplemachines.org/community/index.php?topic=279945.msg3307380#msg3307380
but I'm not allowed to post attachments there.
I have made a mod to replace the US style week numbering in the calender with the ISO 8601 week numbering standard.
It seems to sort of work, but no guarantees, since this is my first mod.
I'm aware of at least one bug.

emanuele

Hello andershz!

I'm posting here to remember the topic! :)


Take a peek at what I'm doing! ;D




Hai bisogno di supporto in Italiano?

Aiutateci ad aiutarvi: spiegate bene il vostro problema: no, "non funziona" non è una spiegazione!!
1) Cosa fai,
2) cosa ti aspetti,
3) cosa ottieni.

andershz

#2
A bit better, but I wouldn't use it in flight control operations just yet...

Marcus Forsberg

Have you submitted this to the mod site? :)

andershz

#4
Not yet, I'm waiting to see if I get any feedback on this first.
I might be on the completely wrong track with this mod since I'm new to SMF development.
If it appears to work reasonably well for someone else than me then I'll submit it.

Also, I'm considering rewriting it in a different way.
Currently the week number is calculated separately in two different places, one for the week view and one for the first week in the month view.
Then in the month view the week number is incremented by one for each row which creates a lot of special cases around new year.
It might be better to make one function that calculates the correct week number for a given day and call it every time a week number is needed.

 

bdzpete

Hi AnderzHZ,

Test feedback:
On Admin|Current Theme|Member Options  the week start day is fixed as Monday.  Good, but perhaps it might be more user friendly not to display that line at all?
However:
- in the week view for week 18 (starts 30 April), the Monday is shown as day 0.
- when clicking >> (next) to forward through the weeks starting this week, it all works until the week starting 22 Oct 2012, which "nexts" to itself.  Same for the week starting 21 Oct 2013.  I haven't checked further.

I don't know how to create SMF packages, but replacing the 2 lines that use strftime("%U") by their equivalent strftime("%V") and forcing $nWeekAdjust = 0 worked 100% for me.

But strftime("%V") isn't available in all PHP environments, so I additionally had to use the workaround suggested at hxxp:uk3.php.net/manual/en/function.strftime.php [nonactive]  to add function week_isonumber. 

Maybe you can incorporate some of the ideas with yours.  I don't know if I'm allowed to post PHP code directly to this forum.  I'll try it in an immediately-following post.


bdzpete

// Returns ISO-8601 weeknumber when strftime("%V") is not available (see hxxp:uk3.php.net/manual/en/function.strftime.php [nonactive])
function week_isonumber ($time) { 
// hxxp:en.wikipedia.org/wiki/ISO_8601 [nonactive] : week 1 is "the week with the year's first Thursday in it (the formal ISO definition)"
   $year = strftime("%Y", $time);
    $first_day = strftime("%w", mktime(0, 0, 0, 1, 1, $year));
    $last_day = strftime("%w", mktime(0, 0, 0, 12, 31, $year));
    $number = $isonumber = strftime("%W", $time);
    // According to strftime("%W"), 1st of january is in week 1 if and only if it is a monday
    if ($first_day == 1)
        $isonumber--;
    // 1st of january is between monday and thursday; starting (now) at 0 when it should be 1
    if ($first_day >= 1 && $first_day <= 4)
        $isonumber++;
    else if ($number == 0)
        $isonumber = week_isonumber(mktime(0, 0, 0, 12, 31, $year - 1));
    if ($isonumber == 53 && ($last_day == 1 || $last_day == 2 || $last_day == 3))
        $isonumber = 1;
    return sprintf("%02d", $isonumber);
}
//  end for ISO-8601 Pete


//         'week_num' => (int) strftime('%U', mktime(0, 0, 0, $month, 1, $year)),
//         'week_num' => (int) strftime('%V', mktime(0, 0, 0, $month, 1, $year)), // but %V n/a in EasyPHP
         'week_num' => (int) week_isonumber(mktime(0, 0, 0, $month, 1, $year)), // for ISO-8601 Pete


   else
      $nWeekAdjust = 0;
   $nWeekAdjust = 0;  // for ISO-8601  Pete


//      $calendarGrid['week_number'] = (int) strftime('%U', mktime(0, 0, 0, $month, $day, $year)) + $nWeekAdjust;
//      $calendarGrid['week_number'] = (int) strftime('%V', mktime(0, 0, 0, $month, $day, $year)), // but %V n/a in EasyPHP
      $calendarGrid['week_number'] = (int) week_isonumber(mktime(0, 0, 0, $month, $day, $year)); // for ISO-8601 Pete




andershz

Quote from: bdzpete on April 13, 2012, 12:28:07 PM
- in the week view for week 18 (starts 30 April), the Monday is shown as day 0.
- when clicking >> (next) to forward through the weeks starting this week, it all works until the week starting 22 Oct 2012, which "nexts" to itself.  Same for the week starting 21 Oct 2013.  I haven't checked further.
ou can incorporate some of the ideas with yours.  I don't know if I'm allowed to post PHP code directly to this forum.  I'll try it in an immediately-following post.

That's odd. Both weeks look OK in my calender.
Maybe different php versions or locale settings.
Back to the drawing board.

bdzpete

A little more info for you:
The "Monday 0 May" effect (and no month break between that Monday & Tuesday 1 May) only occurs in systems where PHP does not support strftime("%V").  Additional symptoms are that every month has week numbers 1-4, or sometimes 1-5.
If strftime("%V") is supported, that Monday is reported correctly as 30 April with a month break before the Tuesday, and week numbers increment correctly throughout the full year. 
So the Monday 0 May effect occurs only with certain implementations of PHP.
 
In my tests, regardless of whether or not strftime("%V") is supported, clicking the >> (next week) symbol at the right end of the header bar in the week view for week 43 still forwards to itself in both 2012 and 2013. 

clicking << (previous week) through all weeks of 2013 & 2012 hits no problems of backwards to self.

My PHP versions are:
5.3.8 (at my web hosting company) where strftime("%V") is supported
5.3.4 in EasyPHP on my own PC, where strftime("%V") is not supported

My time zone is Europe/London.  Currently UTC+1.

bdzpete

And more.  We're getting close.

This is the "forward to itself" HTTP call (from the Apache log):
GET /forum/index.php?action=calendar;viewweek;year=2012;month=10;day=22 HTTP/1.1" 200 3401
It looks like $calendarGrid['next_week']['day']  is not being incremented correctly.
This is set in only 1 place:
$curTimestamp = mktime(0, 0, 0, $month, $day, $year);
$nextWeekTimestamp = $curTimestamp + 604800;
$calendarGrid['next_week']['day'] = (int) strftime('%d', $nextWeekTimestamp);

I think this has to do with the end of daylight saving (28 Oct 2012 here in the UK).  Instead of week 43 having 604800 seconds (168hrs), it is effectively 608400 seconds (169hrs).
I have no idea how to handle this in PHP.  Any ideas?

bdzpete

The following fixes the "end of daylight savings" problem (without introducing another at the start of daylight savings).  Add the middle 2 lines of the following to your existing ISOweek mod:

   $nextWeekTimestamp = $curTimestamp + 604800;
   $tzoneadj = date('I',$nextWeekTimestamp) - date('I',$curTimestamp);
   $nextWeekTimestamp = $nextWeekTimestamp - $tzoneadj * 3600;
   $calendarGrid['next_week']['day'] = (int) strftime('%d', $nextWeekTimestamp);

So we're all sorted except where strftime("%V") isn't supported.  For which (perhaps) you could implement that week_isonumber function I suggested earlier.

And I also owe you an apology: The Monday 0 May phenomenon was due to me installing your ISOweek usermod on top of some temporary changes I had made manually to Subs-Calendar.  I forgot to remove those changes before the install of ISOweek.  After doing a clean install of the ISOweek mod,  Monday 30 April correctly appears.

bdzpete

Hi Andershz,

Wikipedia explains that for those countries that implement daylight saving, the time shift is normally one hour.  However it can be less, and in the past it has been up to 2 hours.  My suggested change above assumed a fixed 1 hour shift.

Because $curTimestmp matches the very first second of the day, $nextWeekTimestamp does also.  But $nextWeekTimestamp is used only to calculate year, month, and day (and never hour, minute or second), so any value in the same day would yield the same results.  My proposed latest change below adds 3 hours (sufficient for all current and historical daylight saving shifts), but anything up to 20 or so would work too.

    $curTimestamp = mktime(0, 0, 0, $month, $day, $year);
    $nextWeekTimestamp = $curTimestamp + 604800;
    // add 3 hours to support possible end of daylight saving during this week
    //   (enough to support all current & historical daylight saving shifts)
    $nextWeekTimestamp = $nextWeekTimestamp + 3*60*60;

    $calendarGrid['next_week']['day'] = (int) strftime('%d', $nextWeekTimestamp);

Note: I think changing $curTimestamp = mktime(0, 0, 0, $month, $day, $year);
        to $curTimestamp = mktime(3, 0, 0, $month, $day, $year);
would achieve the same result, but might be much more difficult to explain in the comments.

I think that's all I can contribute.  Over to you anders to polish the code and create the SMF mod package.

andershz

Thanks Pete,

The week number issue has been dormant for so long, all it took was for someone to poke it to get things going again.
So even if not much of my original attempt will actually be used it was not in vain.

bdzpete

An afterthought on my closing "would achieve the same result" comment:

I no longer think that $curTimestamp = mktime(3, 0, 0, $month, $day, $year); would achieve the same result.  It would probably just push the problem into another week.  To avoid the problem completely,  $nextWeekTimestamp has to be ahead of $curTimestamp by at least 1 week + whatever the daylight saving time shift is in that locale.

... but you probably realised that yourself.

Looking forward to trying out the final ISOweek mod.

Cheers,
Pete

andershz

Some further investigation reveals that the "link to self" problem in for instance week 44 2012, (week 45 if using Sunday as first day of week),
is not a problem in my mod. This error is there in the original code.
I don't fancy fiddling around with DST hours when all I want to know is the week number, that feels like working on the wrong level.
So my current approach is to replace
$nextWeekTimestamp = $curTimestamp + 604800;
with
$nextWeekTimestamp = mktime(0, 0, 0, $month, $day+7, $year);
It seems to work ok, because mktime is smart enough to handle things like December 38th.

At first I also thought that I've found a fault in the calculating of US style week numbers that I attempted to correct,
but after a lot of research it seems that there is no right or wrong in this case.
A search for "US week numbers" gives numerous hits for pages claiming to calculate the week number.
Funnily all of them give different results.
So I have concluded that while the ISO standard for calculation of week numbers is well defined, the US style week numbers seems to be a matter of personal opinion,
so I will not touch that part of the code.


bdzpete

Quote from: andershz on April 18, 2012, 02:32:48 AM
replace
$nextWeekTimestamp = $curTimestamp + 604800;
with
$nextWeekTimestamp = mktime(0, 0, 0, $month, $day+7, $year);

Brilliant!  Why didn't I think of that.  Best of all, it works.  I tested with both forwards & backwards links to the next|previous week, in both 2012 and 2013, in both of my PHP environments.

MrPhil

#16
There is a long discussion of some of the peculiarities of %V in strftime() in http://us2.php.net/manual/en/function.strftime.php . It's a standard field and is available on all systems, but evidently has some odd behavior in certain cases.

Add: OK, evidently %V is not always available, despite what the first paragraph says in the documentation. Be sure to read the strftime() documentation and discussion thoroughly before using strftime() calls.

andershz

Quote from: MrPhil on April 18, 2012, 09:49:13 AM
...
It's a standard field and is available on all systems...

True, but only for a limited definition of "all", which apparently doesn't include Microsoft Windows.
Personally I don't see that as a problem since I only use Linux servers, but there are others less fortunate.

andershz

#18
New version.

I have added a new checkbox under "Look and Layout" in the users profile settings: "Use ISO week numbers in the calendar when Monday is set as first day of the week".
ISO week numbering will be used if, and only if, this checkbox is checked and Monday is set as first day of the week,
otherwise the original week numbering code is used, with one exception:
The "link to self" error in for instance week 44 2012, (week 45 if using Sunday as first day of week), is solved by replacing
$nextWeekTimestamp = $curTimestamp + 604800;
with
$nextWeekTimestamp = mktime(0, 0, 0, $month, $day+7, $year);
To get around the problem that not all systems implement the strftime %V option the ISO week numbering now uses the code from the php strftime manual, mentioned by bdzpete in a previous post.

MrPhil

Very strange. Assuming $curTimestamp is a signed 32 bit integer, its current value should be around 1.3E+09, so adding 1 week's worth of seconds shouldn't overflow anything (nor force a change to floating point and possible loss of precision). The $curTimestamp is at midnight (00:00:00), so $nexWeekTimestamp should have exactly the same value either way. Have you confirmed that your change handles end-of-month wraparound OK (e.g., April 33rd)? According to the mktime() reference, it should, but in practice who knows?

I don't see %V being used in standard SMF code. Is it only used by a mod?

Advertisement: