问题
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?
回答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.
回答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.
回答3:
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;
来源:https://stackoverflow.com/questions/30065083/delphi-week-number-function-based-on-system-start-of-week