Week of month doesn't seem correct

I was trying to adapt this tutorial on making calendar plots in ggplot2 but running into something quirky with Week of months. See the code below. Why is 2025-05-01 listed as week 5?

library(tidyverse)
dates_may_2025 <- tibble(
  date=seq(mdy("05-01-2025"), mdy("05-31-2025"), by="1 day")
) %>%
  mutate(
    day = mday(date),
    month = month(
      date, label = T, abbr = F, locale = 'en_US.UTF-8'
    ),
    wday = wday(date, label = T, locale = 'en_US.UTF-8'),
    week_month = stringi::stri_datetime_fields(date, locale = 'en_US.UTF-8')$WeekOfMonth,
    week_year = week(date)
  )
dates_may_2025 %>% print(n=31)
#> # A tibble: 31 × 6
#>    date         day month wday  week_month week_year
#>    <date>     <int> <ord> <ord>      <int>     <dbl>
#>  1 2025-05-01     1 May   Thu            5        18
#>  2 2025-05-02     2 May   Fri            1        18
#>  3 2025-05-03     3 May   Sat            1        18
#>  4 2025-05-04     4 May   Sun            1        18
#>  5 2025-05-05     5 May   Mon            2        18
#>  6 2025-05-06     6 May   Tue            2        18
#>  7 2025-05-07     7 May   Wed            2        19
#>  8 2025-05-08     8 May   Thu            2        19
#>  9 2025-05-09     9 May   Fri            2        19
#> 10 2025-05-10    10 May   Sat            2        19
#> 11 2025-05-11    11 May   Sun            2        19
#> 12 2025-05-12    12 May   Mon            3        19
#> 13 2025-05-13    13 May   Tue            3        19
#> 14 2025-05-14    14 May   Wed            3        20
#> 15 2025-05-15    15 May   Thu            3        20
#> 16 2025-05-16    16 May   Fri            3        20
#> 17 2025-05-17    17 May   Sat            3        20
#> 18 2025-05-18    18 May   Sun            3        20
#> 19 2025-05-19    19 May   Mon            4        20
#> 20 2025-05-20    20 May   Tue            4        20
#> 21 2025-05-21    21 May   Wed            4        21
#> 22 2025-05-22    22 May   Thu            4        21
#> 23 2025-05-23    23 May   Fri            4        21
#> 24 2025-05-24    24 May   Sat            4        21
#> 25 2025-05-25    25 May   Sun            4        21
#> 26 2025-05-26    26 May   Mon            5        21
#> 27 2025-05-27    27 May   Tue            5        21
#> 28 2025-05-28    28 May   Wed            5        22
#> 29 2025-05-29    29 May   Thu            5        22
#> 30 2025-05-30    30 May   Fri            5        22
#> 31 2025-05-31    31 May   Sat            5        22

Created on 2025-04-29 with reprex v2.1.1

And it seems the first day of the month is never week 1?

library(tidyverse)
dates_2025 <- tibble(
  date=seq(mdy("01-01-2025"), mdy("12-31-2025"), by="1 day")
) %>%
  mutate(
    day = mday(date),
    month = month(
      date, label = T, abbr = F, locale = 'en_US.UTF-8'
    ),
    wday = wday(date, label = T, locale = 'en_US.UTF-8'),
    week_month = stringi::stri_datetime_fields(date, locale = 'en_US.UTF-8')$WeekOfMonth,
    week_year = week(date)
  )

dates_2025 %>% count(day, week_month) %>% 
  arrange(week_month) %>%
  pivot_wider(values_from=n, names_from=week_month, names_prefix = "week_") %>% 
  arrange(day) %>% 
  print(n=31)
#> # A tibble: 31 × 7
#>      day week_1 week_2 week_3 week_4 week_5 week_6
#>    <int>  <int>  <int>  <int>  <int>  <int>  <int>
#>  1     1     NA     NA     NA     NA      9      3
#>  2     2     12     NA     NA     NA     NA     NA
#>  3     3      9      3     NA     NA     NA     NA
#>  4     4      8      4     NA     NA     NA     NA
#>  5     5      7      5     NA     NA     NA     NA
#>  6     6      5      7     NA     NA     NA     NA
#>  7     7      3      9     NA     NA     NA     NA
#>  8     8      1     11     NA     NA     NA     NA
#>  9     9     NA     12     NA     NA     NA     NA
#> 10    10     NA      9      3     NA     NA     NA
#> 11    11     NA      8      4     NA     NA     NA
#> 12    12     NA      7      5     NA     NA     NA
#> 13    13     NA      5      7     NA     NA     NA
#> 14    14     NA      3      9     NA     NA     NA
#> 15    15     NA      1     11     NA     NA     NA
#> 16    16     NA     NA     12     NA     NA     NA
#> 17    17     NA     NA      9      3     NA     NA
#> 18    18     NA     NA      8      4     NA     NA
#> 19    19     NA     NA      7      5     NA     NA
#> 20    20     NA     NA      5      7     NA     NA
#> 21    21     NA     NA      3      9     NA     NA
#> 22    22     NA     NA      1     11     NA     NA
#> 23    23     NA     NA     NA     12     NA     NA
#> 24    24     NA     NA     NA      9      3     NA
#> 25    25     NA     NA     NA      8      4     NA
#> 26    26     NA     NA     NA      7      5     NA
#> 27    27     NA     NA     NA      5      7     NA
#> 28    28     NA     NA     NA      3      9     NA
#> 29    29     NA     NA     NA      1     10     NA
#> 30    30     NA     NA     NA     NA     11     NA
#> 31    31     NA     NA     NA     NA      6      1

Created on 2025-04-29 with reprex v2.1.1

Note that DayOfWeek also appears to be off by 1. And May 2, 2025 shows WeekOfMonth as 1.

I can identify (but not necessarily explain) part of the problem.

library(stringi)
library(tidyverse)
d <- mdy("05-01-2025")
d |> str()
#>  Date[1:1], format: "2025-05-01"
stri_datetime_fields(d, locale = 'en_US.UTF-8')
#>   Year Month Day Hour Minute Second Millisecond WeekOfYear WeekOfMonth
#> 1 2025     4  30   20      0      0           0         18           5
#>   DayOfYear DayOfWeek Hour12 AmPm Era
#> 1       120         4      8    2   2
stri_datetime_fields(d, tz = "Etc/GMT-0", locale = 'en_US.UTF-8')
#>   Year Month Day Hour Minute Second Millisecond WeekOfYear WeekOfMonth
#> 1 2025     5   1    0      0      0           0         18           1
#>   DayOfYear DayOfWeek Hour12 AmPm Era
#> 1       121         5      0    1   2

Created on 2025-04-29 with reprex v2.1.1

So it seems that there is some expectation in stri_datetime_fields that the input date is midnight GMT? Note that your May 1 magically morphs into April 30, which does indeed fall in the fifth week of April. This also explains why Similarly, I assume your first of every month (GMT) becomes last day of previous month (any US time zone), hence never in the first week of that month. This also explains why Thursday May 1 becomes day of week 4 (Wednesday), since it is now discounted to April 30. Something to do with tariffs??

Aha. Try adding a time zone to the mdy function.

library(stringi)
library(tidyverse)
d <- mdy("05-01-2025", tz = "America/New_York")
d |> str()
#>  POSIXct[1:1], format: "2025-05-01"
stri_datetime_fields(d, locale = 'en_US.UTF-8')
#>   Year Month Day Hour Minute Second Millisecond WeekOfYear WeekOfMonth
#> 1 2025     5   1    0      0      0           0         18           1
#>   DayOfYear DayOfWeek Hour12 AmPm Era
#> 1       121         5      0    1   2

Created on 2025-04-29 with reprex v2.1.1

4 Likes