0

The DateUtils.WeekOfTheYear function is great but it uses the ISO 8601 standard definition of a week. That is, a week is considered to start on a Monday and end on a Sunday. I need a similar function that determines the week number based on the system setting for the start of the week. Or at least for either a sunday or monday start of week like MySQL's week function. Anyone have such a function?

fullerm
  • 406
  • 1
  • 8
  • 23
  • You have the source for DateUtils.WeekOfTheYear. Can't you use that as a basis for at least an effort to write one yourself? – Ken White May 05 '15 at 23:50
  • Please read http://stackoverflow.com/help/on-topic *Questions asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead, describe the problem and what has been done so far to solve it.* – David Heffernan May 05 '15 at 23:58
  • @Ken, you are of course assuming that OP is not using Starter Edition (no source). ;) – Deltics May 06 '15 at 03:05
  • @Deltics: The poster mentions exactly how it works, and if they are using the starter edition that does not contain code they could have indicated that in either the text or the tags. Since starter is less common than the other editions, I operate under the presumption that they are using a regular edition unless otherwise stated, just like I presume they are using one of the more recent versions when answering. I also presume that they should make an effort to come up with a solution prior to asking here just like everyone else. :-) – Ken White May 06 '15 at 03:07
  • 1
    @Ken - the poster quotes the Delphi documentation. Verbatim! (apart from the "is great" part). :) There is nothing to suggest they have looked at the source. And I thought the modern approach to development was to maximise code re-use and avoid re-inventing wheels. ;) Either way, I saw this an an opportunity to indulge in a little coding exercise for myself and hopefully help out a fellow Delphi coder in the process. :) I don't see the harm. – Deltics May 06 '15 at 06:53

4 Answers4

2

ISO-8601 includes a great deal more than just the first day of the week in its specifications for these calculations. There are also rules which determine the first week in the year, for example.

It is not clear whether what you are looking for is a function to replicate the ISO-8601 calculation with these rules otherwise intact and solely varying the first day of the week, or a direct equivalent of the WEEK() function of MySQL, or something else only similar (and not fully defined).

Worth noting is that the MySQL WEEK() function accepts a parameter which does not determine an arbitrary day marking the start of the week, rather it indicates whether either of Sunday or Monday is to be used along with changing a number of other rules that determine the calculated result.

By contrast, the system setting for first day of the week on Windows itself can be ANY day of the week that the user wishes - Mon, Tue, Wed, Thu, Fri, Sat or Sun.

The implementation I provide below is a simple calculation (some might call it naive) which simply returns a value 0..53 based on the number of week periods, or part periods, elapsed between a date specified and the start of the year in which that date occurs.

The week in which 1st of Jan occurs for the year containing the specified date is deemed to be week 0.

Therefore if the 1st of Jan occurs on a Sunday and the "start of week" is defined as Monday then:

Sun, 01-Jan  = Week 0
Mon, 02-Jan  = Week 1
..
Sun, 08-Jan  = Week 1
Mon, 09-Jan  = Week 2
..
etc

The Implementation

I have split the implementation into two distinct parts.

The first (WeeksSince01Jan) accepts a date and a parameter indicating the day of week to be considered the first day of the week in the calculation.

This parameter takes a TSYSDayOfWeek value - an enum arranged such that the ordinal values for each day correspond to the values used in system locale settings for the days of the week. (The value returned by the RTL DayOfWeek function uses different values, defined in this code as TRTLDayOfWeek).

The second part of the implementation provides a LocaleWeeksSince01Jan, to demonstrate obtaining the locale defined first day of week for the current user. This is then simply passed thru to a call to WeeksSince01Jan.

type
  TSYSDayOfWeek = (sdMon, sdTue, sdWed, sdThu, sdFri, sdSat, sdSun);
  TRTLDayOfWeek = 1..7;    // Sun -> Sat


function WeeksSince01Jan(const aDate: TDateTime;
                         const aFirstDayOfWeek: TSYSDayOfWeek): Word;
const
  LOCALE_DOW : array[TRTLDayOfWeek] of TSYSDayOfWeek = (sdSun, sdMon, sdTue, sdWed, sdThu, sdFri, sdSat);
var
  y, m, d: Word;
  dayOfYearStart: TSYSDayOfWeek;
  dtYearStart: TDateTime;
  dtStartOfFirstWeekInYear: TDateTime;
  iAdjust: Integer;
begin
  // Get the date for the first day of the year and determine which
  //  day of the week (Mon-Fri) that was

  DecodeDate(aDate, y, m, d);
  dtYearStart     := EncodeDate(y, 1, 1);
  dayOfYearStart  := LOCALE_DOW[DayOfWeek(dtYearStart)];

  // Week calculation is simply the number of 7 day periods
  //  elapsed since the start of the year to the specified date,
  //  adjusted to reflect any 'offset' to the specified first day of week.

  iAdjust := Ord(dayOfYearStart) - Ord(aFirstDayOfWeek);
  result  := (((Trunc(aDate) + iAdjust) - Trunc(dtYearStart)) div 7);
end;


function LocaleWeeksSince01Jan(const aDate: TDateTime): Word;
var
  localeValue: array[0..1] of Char;
  firstDayOfWeek: TSYSDayOfWeek;
begin
  // Get the system defined first day of the week

  GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, localeValue, SizeOf(localeValue));
  firstDayOfWeek := TSYSDayOfWeek(Ord(localeValue[0]) - Ord('0'));

  result := WeeksSince01Jan(aDate, firstDayOfWeek);
end;

If you have more complex rules to determine the 0th or 1st week of a year based on numbers of days in that week etc, then you will need to modify this implementation accordingly. There is no attempt to accommodate such needs in the current implementation.

For Testing

The code below may be used as the basis for testing the output (using the system defined first day of the week):

const
  YEAR = 2012;
var
  d: Integer;
  dt: TDateTime;
  wk: Word;
begin
  List.Items.Clear;

  dt := EncodeDate(YEAR, 1, 1) - 7;

  for d := 1 to 380 do
  begin
    dt := dt + 1;
    wk := LocaleWeeksSince01Jan(dt);
    List.Items.Add(Format('%s, %s = week %d', [ShortDayNames[DayOfWeek(dt)],
                                               DateToStr(dt),
                                               wk]));
  end;

Where List is a reference to a TListbox.

Change the value of YEAR to produce a range of results that cover all dates in the specified year +/- an additional 7/8 days, to illustrate the change in result at year end of the preceding and succeeding years.

NOTE: 2012 is a year which demonstrates the possibility of returning dates in that year covering the full range of potential results, 0..53.

Deltics
  • 22,162
  • 2
  • 42
  • 70
1

If you are only interested in week starting on Sunday instead on Monday you can simply substract 1 day from your DateTime value before feeding it to DateUtils.WeekOfTheYear function.

EDIT: Response to David Heffernan comment:

Imagine what happens when you subtract 1 from January 1st

It depends on which day is on January 1st

From Embarcadero documentation: http://docwiki.embarcadero.com/Libraries/XE8/en/System.DateUtils.WeekOfTheYear

AYear returns the year in which the specified week occurs. Note that this may not be the same as the year of AValue. This is because the first week of a year is defined as the first week with four or more days in that year. This means that, if the first calendar day of the year is a Friday, Saturday, or Sunday, then for the first three, two, or one days of the calendar year, WeekOfTheYear returns the last week of the previous year. Similarly, if the last calendar day of the year is a Monday, Tuesday, or Wednesday, then for the last one, two, or three days of the calendar year, WeekOfTheYear returns 1 (the first week of the next calendar year).

So if the week starts with Sunday instead of Monday then it means that week start and end days are simply shifted by one day backward.

So for such occasions it is best to use over-ridden version with additional variable parameter to which the year that this week belongs to is stored.

Rohit Gupta
  • 4,022
  • 20
  • 31
  • 41
SilverWarior
  • 7,372
  • 2
  • 16
  • 22
  • 1
    No. Imagine what happens when you subtract 1 from January 1st. – David Heffernan May 06 '15 at 10:55
  • @DavidHeffernan I have edited my answer with more information on explaining why this would work and thus also answered your question. – SilverWarior May 06 '15 at 12:56
  • 1
    the day before Jan 1 is the final day of December in the previous year. – David Heffernan May 06 '15 at 13:05
  • Yes I know. But the result would still be the correct one. Take this year (2015) as any example as it is perfect. For those that have Monday as start day of the week (like me) the January 1st belongs to the first week of the year because four days of that week are in current year. But for those that have Sunday as starting day of the week it would meant that January 1st would actually belong to last week of the previous year because only three days of that weak are in current year. – SilverWarior May 06 '15 at 13:20
  • @DavidHeffernan You might have figured this out earlier if you would approach to the problem from the same angle that I did. You see when I approached to this problem I quickly found out that the main problem is two different aproaches of treating a week. You can consider these two approaches as two different sized dimensions with different offset. So you want to go and allign these two dimensions which you would do by shifting position of one of them for negative offset value. ... – SilverWarior May 06 '15 at 13:43
  • ... And by shinfting the dimension you would also shift the position of any point that is on this dimension by same value. That is why I siomply shifted the inputdate (point on one dimension) by negative offset from other dimension position (1 day difference in treating on which day does the week starts). – SilverWarior May 06 '15 at 13:45
  • Similar appraoch is used when shifting between different calander types becouse each calander type is treated as seperate dimension which you are then alligning to each other. – SilverWarior May 06 '15 at 13:47
  • I think you have to add 1 not subtract – Yousef Albakoush Jan 20 '20 at 02:13
0

I've combined Deltics' great code with SilverWarior's simple idea to create a WeekOfYear function that handles the system week start day.

type
  TSYSDayOfWeek = (sdMon, sdTue, sdWed, sdThu, sdFri, sdSat, sdSun);

function LocaleWeekOfTheYear(dte: TDateTime): word;
var
  localeValue: array[0..1] of Char;
  firstDayOfWeek: TSYSDayOfWeek;
  yearOld,yearNew: word;
  dteNew: TDateTime;
begin
  // Get the system defined first day of the week
  GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, localeValue, SizeOf(localeValue));
  firstDayOfWeek := TSYSDayOfWeek(Ord(localeValue[0]) - Ord('0'));
  yearOld:= Year(dte);
  if (firstDayOfWeek=sdSun) then
    dteNew:= dte-1
  else 
    dteNew:= dte+Ord(firstDayOfWeek);
  yearNew:= Year(dteNew);
  if (yearOld=yearNew) then
    dte:= dteNew;
  Result:= WeekOfTheYear(dte);
end;
fullerm
  • 406
  • 1
  • 8
  • 23
0

To make the first day of the week is Saturday, I use to add 2 to the value of Now date.

As example WeekOf(Now + 2) : makes the first day is Saturday, WeekOf(Now + 2) : makes the first day is Sunday, WeekOf(Now + 0) : makes the first day is Monday,