¿Cómo calculo el número de semana dada una fecha?

19 minutos de lectura

¿Cómo calculo el número de semana dada una fecha?
Gran GH

Si tengo una fecha, ¿cómo calculo el número de semana para esa fecha dentro de ese año?

Por ejemplo, en 2008, del 1 al 6 de enero están en la semana 1 y del 7 al 13 de enero están en la semana 2, así que si mi fecha fuera el 10 de enero de 2008, mi número de semana sería 2.

Un algoritmo sería excelente para comenzar y el código de muestra también ayudaría: estoy desarrollando en C ++ en Windows.

Relacionados:

¿Obtener el número de semana fuera de una fecha en MS SQL Server 2005?

¿Cómo calculo el número de semana dada una fecha?
jonathan leffler

Tenga en cuenta que mientras su definición de enésimo semana del año es sostenible, tampoco es ‘la’ estándar.

ISO 8601 define un estándar para la representación de fechas, horas y zonas horarias. Define semanas que comienzan en lunes. También dice que la semana 1 de un año es la que contiene al menos 4 días del año dado. En consecuencia, los días 29, 30 y 31 de diciembre de 20xx podrían estar en la semana 1 de 20xy (donde xy = xx + 1), y los días 1, 2 y 3 de enero de 20xy podrían estar todos en la última semana de 20xx. Además, puede haber una semana 53.

[Added: note that the C standard and the `strftime() function provides for weeks that start on Sunday as well as weeks that start on Monday. It is not clear that the C standard provides for the year number of week 0 for Sunday-based weeks. See also the answer from Emerick Rogul.]

Luego viene la interesante fase de prueba: ¿cuándo llega la semana 53? Una respuesta es el viernes 1 de enero de 2010, que está en 2009-W53 (como, de hecho, es el domingo 3 de enero de 2010). De manera similar, el sábado 1 de enero de 2005 está en 2004-W53, pero el domingo 1 de enero de 2006 está en 2005-W52.

Ese es un extracto de un comentario en el siguiente código, que en realidad está en Informix SPL (Lenguaje de procedimiento almacenado), pero se puede leer, aunque probablemente no se pueda escribir, sin mucha más explicación. El ‘||’ operador es la operación de concatenación de cadenas SQL, y el domingo es el día 0, el lunes es el día 1, … el sábado es el día 6 de la semana. Hay notas extensas en los comentarios, incluido el texto relevante de la norma. Los comentarios de una línea comienzan ‘--‘; posiblemente los comentarios de varias líneas comiencen con ‘{‘ y terminar en el siguiente ‘}‘.

-- @(#)$Id: iso8601_weekday.spl,v 1.1 2001/04/03 19:34:43 jleffler Exp $
--
-- Calculate ISO 8601 Week Number for given date
-- Defines procedure: iso8601_weekday().
-- Uses procedure: iso8601_weeknum().

{
According to a summary of the ISO 8601:1988 standard "Data Elements and
Interchange Formats -- Information Interchange -- Representation of
dates and times":

    The week notation can also be extended by a number indicating the
    day of the week.  For example the day 1996-12-31 which is the
    Tuesday (day 2) of the first week of 1997 can also be written as

        1997-W01-2 or 1997W012

    for applications like industrial planning where many things like
    shift rotations are organized per week and knowing the week number
    and the day of the week is more handy than knowing the day of the
    month.

This procedure uses iso8601_weeknum() to format the YYYY-Www part of the
date, and appends '-d' to the result, allowing for Informix's coding of
Sunday as day 0 rather than day 7 as required by ISO 8601.
}

CREATE PROCEDURE iso8601_weekday(dateval DATE DEFAULT TODAY) RETURNING CHAR(10);
    DEFINE rv CHAR(10);
    DEFINE dw CHAR(4);
    LET dw = WEEKDAY(dateval);
    IF dw = 0 THEN
            LET dw = 7;
    END IF;
    RETURN iso8601_weeknum(dateval) || '-' || dw;
END PROCEDURE;
-- @(#)$Id: iso8601_weeknum.spl,v 1.1 2001/02/27 20:36:25 jleffler Exp $
--
-- Calculate ISO 8601 Week Number for given date
-- Defines procedures: day_one_week_one() and iso8601_weeknum().

{
According to a summary of the ISO 8601:1988 standard "Data Elements and
Interchange Formats -- Information Interchange -- Representation of
dates and times":

    In commercial and industrial applications (delivery times,
    production plans, etc.), especially in Europe, it is often required
    to refer to a week of a year.  Week 01 of a year is per definition
    the first week which has the Thursday in this year, which is
    equivalent to the week which contains the fourth day of January.  In
    other words, the first week of a new year is the week which has the
    majority of its days in the new year.  Week 01 might also contain
    days from the previous year and the week before week 01 of a year is
    the last week (52 or 53) of the previous year even if it contains
    days from the new year.  A week starts with Monday (day 1) and ends
    with Sunday (day 7).  For example, the first week of the year 1997
    lasts from 1996-12-30 to 1997-01-05 and can be written in standard
    notation as

        1997-W01 or 1997W01

    The week notation can also be extended by a number indicating the
    day of the week.  For example the day 1996-12-31 which is the
    Tuesday (day 2) of the first week of 1997 can also be written as

        1997-W01-2 or 1997W012

    for applications like industrial planning where many things like
    shift rotations are organized per week and knowing the week number
    and the day of the week is more handy than knowing the day of the
    month.

Referring to the standard itself, section 3.17 defines a calendar week:

    week, calendar: A seven day period within a calendar year, starting
    on a Monday and identified by its ordinal number within the year;
    the first calendar week of the year is the one that includes the
    first Thursday of that year.  In the Gregorian calendar, this is
    equivalent to the week which includes 4 January.

Section 5.2.3 "Date identified by Calendar week and day numbers" states:

    Calendar week is represented by two numeric digits.  The first
    calendar week of a year shall be identified as 01 [...]

    Day of the week is represented by one decimal digit.  Monday
    shall be identified as day 1 of any calendar week [...]

Section 5.2.3.1 "Complete representation" states:

    When the application clearly identifies the need for a complete
    representation of a date identified by calendar week and day
    numbers, it shall be one of the alphanumeric representations as
    follows, where CCYY represents a calendar year, W is the week
    designator, ww represents the ordinal number of a calendar week
    within the year, and D represents the ordinal number within the
    calendar week.

    Basic format: CCYYWwwD
        Example: 1985W155
    Extended format: CCYY-Www-D
        Example: 1985-W15-5

Both the summary and the formal definition are intuitively clear, but it
is not obvious how to translate it into an algorithm.  However, we can
deal with the problem by exhaustively enumerating the seven options for
the day of the week on which 1st January falls (with actual year values
for concreteness):

    1st January 2001 is Monday    => Week 1 starts on 2001-01-01
    1st January 2002 is Tuesday   => Week 1 starts on 2001-12-31
    1st January 2003 is Wednesday => Week 1 starts on 2002-12-30
    1st January 2004 is Thursday  => Week 1 starts on 2003-12-29
    1st January 2010 is Friday    => Week 1 starts on 2010-01-04
    1st January 2005 is Saturday  => Week 1 starts on 2005-01-03
    1st January 2006 is Sunday    => Week 1 starts on 2006-01-02

(Cross-check: 1st January 1997 was a Wednesday; the summary notes state
that week 1 of 1997 started on 1996-12-30, which is consistent with the
table derived for dates in the first decade of the third millennium
above).

When working with the Informix DATE types, bear in mind that Informix
uses WEEKDAY values 0 = Sunday, 1 = Monday, 6 = Saturday.  When the
weekday of the first of January has the value in the LH column, you need
to add the value in the RH column to the 1st of January to obtain the
date of the first day of the first week of the year.

    Weekday         Offset to
    1st January     1st day of week 1

    0               +1
    1                0
    2               -1
    3               -2
    4               -3
    5               +3
    6               +2

This can be written as MOD(11-w,7)-3 where w is the (Informix encoding
of the) weekday of 1st January and the value 11 is used to ensure that
no negative values are presented to the MOD operator.  Hence, the
expression for the date corresponding to the 1st day (Monday) of the 1st
week of a given year, yyyy, is:

    d1w1 = MDY(1, 1, yyyy) + MOD(11 - WEEKDAY(MDY(1,1,yyyy)), 7) - 3

This expression is encapsulated in stored procedure day_one_week_one:
}

CREATE PROCEDURE day_one_week_one(yyyy INTEGER) RETURNING DATE;
    DEFINE jan1 DATE;
    LET jan1 = MDY(1, 1, yyyy);
    RETURN jan1 + MOD(11 - WEEKDAY(jan1), 7) - 3;
END PROCEDURE;

{
Given this date d1w1, we can calculate the week number of any other date
in the same year as:

    TRUNC((dateval - d1w1) / 7) + 1

The residual issues are ensuring that the wraparounds are correct.  If
the given date is earlier than the start of the first week of the year
that contains it, then the date belongs to the last week of the previous
year.  If the given date is on or after the start of the first week of
the next year, then the date belongs to the first week of the next year.

Given these observations, we can write iso8601_weeknum as shown below.
(Beware: iso8601_week_number() is too long for servers with the
18-character limit; so is day_one_of_week_one()).

Then comes the interesting testing phase -- when do you get week 53?
One answer is on Friday 1st January 2010, which is in 2009-W53 (as,
indeed, is Sunday 3rd January 2010).  Similarly, Saturday 1st January
2005 is in 2004-W53, but Sunday 1st January 2006 is in 2005-W52.
}

CREATE PROCEDURE iso8601_weeknum(dateval DATE DEFAULT TODAY) RETURNING CHAR(8);
    DEFINE rv CHAR(8);
    DEFINE yyyy CHAR(4);
    DEFINE ww CHAR(2);
    DEFINE d1w1 DATE;
    DEFINE tv DATE;
    DEFINE wn INTEGER;
    DEFINE yn INTEGER;
    -- Calculate year and week number.
    LET yn = YEAR(dateval);
    LET d1w1 = day_one_week_one(yn);
    IF dateval < d1w1 THEN
        -- Date is in early January and is in last week of prior year
        LET yn = yn - 1;
        LET d1w1 = day_one_week_one(yn);
    ELSE
        LET tv = day_one_week_one(yn + 1);
        IF dateval >= tv THEN
            -- Date is in late December and is in the first week of next year
            LET yn = yn + 1;
            LET d1w1 = tv;
        END IF;
    END IF;
    LET wn = TRUNC((dateval - d1w1) / 7) + 1;
    -- Calculation complete: yn is year number and wn is week number.
    -- Format result.
    LET yyyy = yn;
    IF wn < 10 THEN
        LET ww = '0' || wn;
    ELSE
        LET ww = wn;
    END IF
    LET rv = yyyy || '-W' || ww;
    RETURN rv;
END PROCEDURE;

Para completar, la función inversa también es fácil de escribir con el day_one_week_one() función anterior:

-- @(#)$Id: ywd_date.spl,v 1.1 2012/12/29 05:13:27 jleffler Exp $
-- @(#)Create ywd_date() and ywdstr_date() stored procedures

-- Convert a date in format year, week, day (ISO 8601) to DATE.
-- Two variants:
-- ywd_date(yyyy SMALLINT, ww SMALLINT, dd SMALLINT) RETURNING DATE;
-- ywdstr_date(ywd CHAR(10)) RETURNING DATE;

-- NB: If week 53 is supplied, there is no check that the year had week
--     53 (GIGO).
-- NB: If year yyyy is a leap year and yyyy-01-01 falls on Wed (3) or
--     Thu (4), there are 53 weeks in the year.
-- NB: If year yyyy is not a leap year and yyyy-01-01 falls on Thu (4),
--     there are 53 weeks in the year.

CREATE PROCEDURE ywd_date(yyyy SMALLINT, ww SMALLINT, dd SMALLINT) RETURNING DATE AS date;
    DEFINE d DATE;
    -- Check ranges
    IF yyyy < 1 OR yyyy > 9999 OR ww < 1 OR ww > 53 OR dd < 1 OR dd > 7 THEN
        RETURN NULL;
    END IF;
    LET d = day_one_week_one(yyyy);
    LET d = d + (ww - 1) * 7 + (dd - 1);
    RETURN d;
END PROCEDURE;

-- Input: 2012-W52-5
CREATE PROCEDURE ywdstr_date(ywd CHAR(10)) RETURNING DATE AS date;
    DEFINE yyyy SMALLINT;
    DEFINE ww   SMALLINT;
    DEFINE dd   SMALLINT;
    LET yyyy = SUBSTR(ywd,  1, 4);
    LET ww   = SUBSTR(ywd,  7, 2);
    LET dd   = SUBSTR(ywd, 10, 1);
    RETURN ywd_date(yyyy, ww, dd);
END PROCEDURE;

CREATE TEMP TABLE test_dates(d DATE);
INSERT INTO test_dates VALUES('2011-12-28');
INSERT INTO test_dates VALUES('2011-12-29');
INSERT INTO test_dates VALUES('2011-12-30');
INSERT INTO test_dates VALUES('2011-12-31');
INSERT INTO test_dates VALUES('2012-01-01');
INSERT INTO test_dates VALUES('2012-01-02');
INSERT INTO test_dates VALUES('2012-01-03');
INSERT INTO test_dates VALUES('2012-01-04');
INSERT INTO test_dates VALUES('2012-01-05');
INSERT INTO test_dates VALUES('2012-01-06');
INSERT INTO test_dates VALUES('2012-01-07');

SELECT d, iso8601_weeknum(d), iso8601_weekday(d), ywdstr_date(iso8601_weekday(d))
  FROM test_dates
 ORDER BY d;

Como se indica en los comentarios, el código aceptará una fecha de la semana 53 incluso si el año solo debe aceptar 52 semanas.

  • Ah, y la versión actual del estándar es ISO/IEC 8601:2004.

    –Jonathan Leffler

    8 de noviembre de 2008 a las 18:33

  • Gracias por la explicación tan detallada, ¡no me había dado cuenta de las diferentes implicaciones y formas de calcular el número de una semana!

    – Gran GH

    10 de noviembre de 2008 a las 22:37

  • ¡Yowza! Me pregunto cuántas personas saben sobre los 4 días… ¡Gracias!

    – Olie

    11 de noviembre de 2008 a las 2:37

¿Cómo calculo el número de semana dada una fecha?
Olie

Pseudocódigo:

int julian = getDayOfYear(myDate)  // Jan 1 = 1, Jan 2 = 2, etc...
int dow = getDayOfWeek(myDate)     // Sun = 0, Mon = 1, etc...
int dowJan1 = getDayOfWeek("1/1/" + thisYear)   // find out first of year's day
// int badWeekNum = (julian / 7) + 1  // Get our week# (wrong!  Don't use this)
int weekNum = ((julian + 6) / 7)   // probably better.  CHECK THIS LINE. (See comments.)
if (dow < dowJan1)                 // adjust for being after Saturday of week #1
    ++weekNum;
return (weekNum)

Para aclarar, este algoritmo asume que numeras tus semanas así:

S  M  T  W  R  F  S
            1  2  3    <-- week #1
4  5  6  7  8  9 10    <-- week #2
[etc.]

getDayOfWeek() y getDayOfYear() son operaciones de objeto de fecha estándar en la mayoría de los idiomas. Si el suyo no los tiene, puede contar hacia adelante desde alguna fecha conocida (el 1 de enero de 1970 es una fecha común), después de mirar hacia arriba para ver qué día de la semana era.

Si va a implementar sus propias rutinas de conteo de fechas, recuerde que los años que son divisibles por 100 son NO años bisiestos, a menos que también sean divisibles por 400. Así que 1900 no fue un año bisiesto, pero 2000 sí lo fue. Si vas a trabajar muy atrás en el tiempo, tienes que jugar con los calendarios gregoriano vs juliano, etc., mira Wikipedia para un montón de información sobre eso.

Este enlace habla sobre las funciones de fecha/hora en Windows/C++ con mayor detalle.

  • Estoy de acuerdo en que no hace lo que pedí, ¡pero me di cuenta de que no había estado pidiendo lo que realmente quería! Mi definición de la semana no. fue uno que pensé que era “estándar”, pero como no lo era, estaba feliz de cambiar mi definición. Creo que su respuesta podría ser la más correcta, pero esta fue la que usé.

    – Gran GH

    20 de noviembre de 2008 a las 16:29

  • A menos que me equivoque, esto no es correcto. Simplemente tome el 7 de enero del ejemplo: juilan = 7, dow = 3, dowJan1 = 4, weekNum = (7/7)+1 = 2. Entonces dow es menor que dowJan1 entonces weekNum+1 = 3. Pero claramente el 7 de enero es Semana 2.

    – NKijak

    5 de marzo de 2011 a las 19:19


  • @NKijak: ¡buena captura! Parece que en lugar de ((julian / 7) + 1), debería usar ((julian + 6) / 7), ¿eso soluciona todo el asunto de “fuera de uno”? De cualquier manera, creo que se puede depurar las sutilezas, la idea básica está ahí. (Si alguien quiere examinar mi solución, editaré la respuesta para que contenga el código corregido).

    – Olie

    16 de marzo de 2011 a las 3:07

  • Incluso con ((julian / 7) + 1) no es correcto: para el 5 de abril de 2012 devuelve 16, en lugar de 15, por ejemplo.

    – phortx

    25 mayo 2015 a las 18:55

  • @phortx: Tenga en cuenta que este ejemplo comenzó como un psuedocódigo escrito en el navegador y ha evolucionado lentamente hasta convertirse en C casi funcional, con comentarios de que algunas partes pueden no ser del todo correctas y requieren ajustes. En particular, la línea que especifique es badWeekNum, mientras que la línea debajo está marcada como “probablemente mejor”. Si tiene una solución, con gusto editaré el ejemplo para mejorarlo cada vez más. ¡Gracias!

    – Olie

    25 mayo 2015 a las 23:26


¿Cómo calculo el número de semana dada una fecha?
Emerick Rogul

Recomiendo enfáticamente usar las funciones de tiempo de la biblioteca estándar de C para calcular el número de semana. Específicamente, el strftime La función tiene especificadores para imprimir el número de la semana (entre muchos otros valores) dada una fecha en desglose (struct tm) formato. Aquí hay un pequeño programa de muestra que ilustra esto:

#include <stdio.h>
#include <string.h>
#include <time.h>

int
main(void)
{
  struct tm tm;
  char timebuf[64];

  // Zero out struct tm
  memset(&tm, 0, sizeof tm);

  // November 4, 2008 11:00 pm
  tm.tm_sec = 0;
  tm.tm_min = 0;
  tm.tm_hour = 23;
  tm.tm_mday = 4;
  tm.tm_mon = 10;
  tm.tm_year = 108;
  tm.tm_isdst = -1;

  // Call mktime to recompute tm.tm_wday and tm.tm_yday
  mktime(&tm);

  if (strftime(timebuf, sizeof timebuf, "%W", &tm) != 0) {
    printf("Week number is: %s\n", timebuf);
  }

  return 0;
}

El resultado de este programa (compilado con GCC en Linux y Microsoft Visual Studio 2005 SP1 en Windows) es:

Week number is: 44

Puedes aprender más sobre strftime aquí.

  • Tenga en cuenta también %U, %G, %u, %V, etc.

    –Jonathan Leffler

    16 de noviembre de 2008 a las 5:48

  • ISO C 90 no es compatible con %U, %G, %u, %V y Visual Studio 2005 (y creo que 2008) siguen C 90 en este sentido, por lo que en Windows no están disponibles. están documentados en opengroup.org/onlinepubs/009695399/functions/strftime.html

    – danio

    19 de febrero de 2009 a las 10:29

  • Mirando la fuente de strftime, este es el cálculo que se utiliza: (t->tm_yday + 7 – (t->tm_wday ? (t->tm_wday – 1) : 6)) / 7 . Usar este cálculo directamente evita tener que convertir a/desde una cadena.

    – Kekoa

    30/04/2013 a las 20:57

struct tm se usa para representar “tiempo desglosado” y tiene al menos los siguientes campos:

int    tm_sec   Seconds [0,60]. 
int    tm_min   Minutes [0,59]. 
int    tm_hour  Hour [0,23]. 
int    tm_mday  Day of month [1,31]. 
int    tm_mon   Month of year [0,11]. 
int    tm_year  Years since 1900. 
int    tm_wday  Day of week [0,6] (Sunday =0). 
int    tm_yday  Day of year [0,365]. 
int    tm_isdst Daylight Savings flag. 

Puede crear una estructura tm a partir de un time_t con la función localtime().

Puede crear un time_t a partir de una estructura tm con la función mktime().

La mejor parte de struct tm es que puede hacer cosas como agregar 24 al miembro del mes del año y cuando llama a mktime() obtendrá un time_t que es 2 años en el futuro (esto funciona con cualquiera de sus miembros, así que puede, por ejemplo, incrementar la hora en 1000 y luego obtener un time_t 41 días en el futuro)…

¿Cómo calculo el número de semana dada una fecha?
Pascual

Lo siento, soy nuevo aquí y no puedo comentar sobre la respuesta en sí, pero el pseudocódigo de la respuesta con la marca de verificación no es completamente correcto.

Pseudocódigo:

int julian = getDayOfYear(myDate)  // Jan 1 = 1, Jan 2 = 2, etc...
int dow = getDayOfWeek(myDate)     // Sun = 0, Mon = 1, etc...
int dowJan1 = getDayOfWeek("1/1/" + thisYear)   // find out first of year's day
int weekNum = (julian / 7) + 1     // Get our week#
if (dow < dowJan1)                 // adjust for being after Saturday of week #1
    ++weekNum;
return (weekNum)

no debe buscar el “primer día del año”, sino el último día del año pasado.

getDayOfWeek("12/31/" + thisYear-1)

sería correcto en lugar de

getDayOfWeek("1/1/" + thisYear) 

Si no hace esto, el último día de la semana del año pasado (como el lunes) siempre estará una semana por delante.

Utilice gmtime o localtime para calcular los días desde el domingo (es decir, el día de la semana) y los días desde el 1 de enero (tenga en cuenta que el 1 de enero es “0” en este último).

La parte arbitraria es decidir en qué día del año comienza la Semana 1: por lo general, solo depende de qué día de la semana fue el 1 de enero, lo que, por supuesto, puede calcular a partir de las dos piezas de información de gmtime. Luego use una tabla de búsqueda para las 7 posibilidades, probablemente sea más fácil que codificar las reglas.

Por ejemplo, creo que Outlook usa el estándar de que la Semana 1 es la primera semana que contiene un jueves. Entonces, si el 1 de enero es domingo, entonces el primer día de la semana 1 es el 1 de enero o el día 0. Las posibilidades restantes son lunes, -1; martes, -2; miércoles, -3; jueves, -4; viernes, 2; Sábado, 1.

Tenga en cuenta los números negativos: “Domingo de la semana 1” en realidad no existe en 4 de los 7 casos, pero si pretendemos que fue un día del año anterior, obtendremos la respuesta correcta.

Una vez que tenga eso, la cantidad de días entre ella y su fecha le indica el número de la semana: divida por 7 y agregue 1.

Dicho esto, me imagino que hay una API de Windows en alguna parte que le dará el mismo número de semana que usa Outlook. Simplemente no sé qué es y, por supuesto, si las reglas de la Semana 1 son diferentes de las de Outlook, entonces probablemente no sirva de mucho.

Código no probado:

int firstdays[7] = { 0, -1, -2, -3, -4, 2, 1 }; // or some other Week 1 rule
struct tm breakdown;
time_t target = time_you_care_about();
_gmtime_s(&breakdown,&target);
int dayofweek = breakdown.tm_wday;
int dayofyear = breakdown.tm_yday;

int jan1wday = (dayofweek - dayofyear) % 7;
if (jan1wday < 0) jan1wday += 7;

int week1first = firstdays[jan1wday];
if (dayofyear < week1first) return 0;
return ((dayofyear - week1first)/7) + 1;

Algo así, de todos modos.

¿Cómo calculo el número de semana dada una fecha?
Mario

Mi definición, que no es ISO 8601 (lo suficientemente buena para mis propósitos y rápida):

// week number of the year
// (Monday as the first day of the week) as a decimal number [00,53].
// All days in a new year preceding the first Monday are considered to be in week 0.
int GetWeek(const struct tm& ts)
{
    return (ts.tm_yday + 7 - (ts.tm_wday ? (ts.tm_wday - 1) : 6)) / 7;
}

¿Ha sido útil esta solución?