Difference between `yyyy` and `YYYY` Java date pattern. What is week-based-year?
Tags: java, programming, bugs
Published: 2022-01-09 10:30:01

No matter what application you are working on, it probably somewhere formats dates as `String`. If you not sure what is the difference between `yyyy` and `YYYY` patterns you might have been surprised in the last week of 2021. Or you even not noticed that something is wrong, because from 2022 everything works fine again. Possible consequence - in my case a component starts rejecting notifications from external service.

The reason was wrong value of calculated sign hash. When I check signed `String` it turned out that date was formatted as `2022-12-28` instead of `2021-12-28`. Question was: why I got 2022 year if there still was 2021? The component worked as follows: received date field was automatically parsed to `Date` object type, later it was formatted as `String` with `DateTimeFormatter` using `YYYY-MM-dd` pattern. To fix problem I just used `yyyy-MM-dd` pattern instead. Difference between `YYYY` and `yyyy` according to [DateTimeFormatter documentation](https://stepniewski.tech/L/2RodCT2Cm):
* `YYYY` is "week-based-year"
* `yyyy` is "year-of-era"

What means "week-based-year"? According to [`WeekFields` Java documentation](https://stepniewski.tech/L/2LBbTJf4Y)

> A week is defined by:
> * The first day-of-week. For example, the ISO-8601 standard considers Monday to be the first day-of-week.
> * The minimal number of days in the first week. For example, the ISO-8601 standard counts the first week as needing at least 4 days.

> The ISO-8601 definition, where a week starts on Monday and the first week has a minimum of 4 days.

For my Locale(`pl-PL`) Java uses [ISO-8601 definitions](https://stepniewski.tech/L/8kVB2x4e):
> There are several mutually equivalent and compatible descriptions of week 01:
> * the week with the starting year's first Thursday in it (the formal ISO definition),
> * the week with 4 January in it,
> * the first week with the majority (four or more) of its days in the starting year, and
> * the week starting with the Monday in the period 29 December - 4 January.

According to this definition the first week of 2022 is week from 3rd January to 9th January.

![December-of-2021](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dctljvbhh3c931qs7ckl.png)

All the following assertions are true.

```java
@Test
@DisplayName("Locale.PL uses WeekFields.ISO")
public void localePlUsesWeekFieldsIso() {
    DateTimeFormatter formatterPl = DateTimeFormatter.ofPattern("YYYY-MM-dd ww", Locale.forLanguageTag("pl-PL"));
    assertEquals(LocalDate.of(2021, 12, 25).format(formatterPl), "2021-12-25 51");
    assertEquals(LocalDate.of(2021, 12, 26).format(formatterPl), "2021-12-26 51");
    assertEquals(LocalDate.of(2021, 12, 27).format(formatterPl), "2021-12-27 52");
    assertEquals(LocalDate.of(2021, 12, 28).format(formatterPl), "2021-12-28 52");
    assertEquals(LocalDate.of(2021, 12, 29).format(formatterPl), "2021-12-29 52");
    assertEquals(LocalDate.of(2021, 12, 30).format(formatterPl), "2021-12-30 52");
    assertEquals(LocalDate.of(2021, 12, 31).format(formatterPl), "2021-12-31 52");
    assertEquals(LocalDate.of(2022, 1, 1).format(formatterPl), "2021-01-01 52");
    assertEquals(LocalDate.of(2022, 1, 2).format(formatterPl), "2021-01-02 52");
    assertEquals(LocalDate.of(2022, 1, 3).format(formatterPl), "2022-01-03 01");
}
```

Pattern `YYYY-MM-dd ww` prints week-based-year (`YYYY`), month-of-year (`MM`), day-of-month (`dd`) and week-of-week-based-year(`ww`). Pay attention that formatter with `YYYY` pattern prints `2021` year for the two first days of 2022: `LocalDate.of(2022, 1, 1)` and `LocalDate.of(2022, 1, 2)` because, according to ISO-8601 definition, these days belong to the last week of `2021` (52nd week of 2021). If you want get day-based-year (or year-of-era) use `yyyy` pattern instead of `YYYY`.

But if the above definition would be always true the component should reject notifications in the first days of 2022 not the last days of 2021.  The catch is that ISO definition of week is not used for all `Locale`s. For example if you use `Locale.US` and run above example, you will be surprised again. Because for `Locale.US` Java uses `Sunday start` definition of week. According to [`WeekFields` Java documentation](https://stepniewski.tech/L/2LBbTJf4Y)

> The common definition of a week that starts on Sunday and the first week has a minimum of 1 day.

According to the above definition the first week is week from 26th December to 1st January. All the following assertions are true.

```Java
@Test
@DisplayName("Locale.US uses WeekFields.SUNDAY_START")
public void localeUsUsesWeekFieldsSundayStart() {
    DateTimeFormatter formatterUs = DateTimeFormatter.ofPattern("YYYY-MM-dd ww", Locale.US);
    assertEquals(LocalDate.of(2021, 12, 25).format(formatterUs), "2021-12-25 52");
    assertEquals(LocalDate.of(2021, 12, 26).format(formatterUs), "2022-12-26 01");
    assertEquals(LocalDate.of(2021, 12, 27).format(formatterUs), "2022-12-27 01");
    assertEquals(LocalDate.of(2021, 12, 28).format(formatterUs), "2022-12-28 01");
    assertEquals(LocalDate.of(2021, 12, 29).format(formatterUs), "2022-12-29 01");
    assertEquals(LocalDate.of(2021, 12, 30).format(formatterUs), "2022-12-30 01");
    assertEquals(LocalDate.of(2021, 12, 31).format(formatterUs), "2022-12-31 01");
    assertEquals(LocalDate.of(2022, 1, 1).format(formatterUs), "2022-01-01 01");
    assertEquals(LocalDate.of(2022, 1, 2).format(formatterUs), "2022-01-02 02");
    assertEquals(LocalDate.of(2022, 1, 3).format(formatterUs), "2022-01-03 02");
}
```

Formatter with `YYYY` pattern prints `2022` starting on 26th December because, according to `Sunday start` definition, these days belong to the first week of `2022`.

In general, week in Java is defined by `WeekFields` class which encapsulates two mentioned earlier "week-defining" values:
> * The first day-of-week. 
> * The minimal number of days in the first week.

If you are interesting which `WeekFields` is used for your `Locale`, you can check it easily. The following code prints all possible `WeekField` definition with all assigned to it `Locale`s.

```Java
public class PrintLocaleWeekFields {

    public static void main(String[] args) {
        Map<WeekFields, List<Locale>> locales = Arrays.stream(Locale.getAvailableLocales())
            .collect(Collectors.groupingBy(WeekFields::of));
        System.out.println(locales);
    }
}
```

Printed result:

```text
WeekFields[MONDAY,1]=[tk_TM_#Latn, en_NU, ff_LR_#Adlm, es_BO, bs_BA, en_LR, ar_TD, nus_SS_#Latn, ff_MR_#Latn, sw_UG, tk_TM, sr_ME_#Cyrl, os_GE_#Cyrl, yo_NG, en_PW, sr_CS, agq_CM_#Latn, ar_EH, bs_BA_#Latn, dje_NE, hy_AM_#Armn, ff_GH_#Latn, fr_PM, ar_KM, agq_CM, tr_TR, kl_GL_#Latn, ar_MR, kl_GL, en_NR, rw_RW_#Latn, en_CY, tr_TR_#Latn, ti_ER, nus_SS, en_RW, hr_HR_#Latn, ln_CD, nnh_CM, dje_NE_#Latn, ksf_CM, fr_VU, nnh_CM_#Latn, fr_NE, bez_TZ_#Latn, ksb_TZ, ln_CF, en_CX, ak_GH_#Latn, en_TZ, fr_NC, fr_CM, pcm_NG_#Latn, teo_UG, ln_CG, az_AZ, el_CY, ku_TR, ff_SN, sq_MK, sr_BA_#Cyrl, so_SO_#Latn, tr_CY, lv_LV_#Latn, uz_UZ_#Latn, dua_CM, fr_TN, sr_RS, sw_TZ_#Latn, fr_PF, pt_GQ, vun_TZ, jmc_TZ, mg_MG_#Latn, en_TV, en_PN, lu_CD_#Latn, en_GY, dyo_SN, nl_CW, fr_GQ, en_NG, fr_CI, ia_001, en_LC, ff_BF_#Adlm, bm_ML_#Latn, yav_CM_#Latn, mk_MK, sl_SI, sg_CF_#Latn, jgo_CM, ff_NE_#Adlm, en_BM, kea_CV, vi_VN, mfe_MU, fr_BF, fr_YT, ff_CM_#Latn, sr_BA_#Latn, uk_UA_#Cyrl, ff_GW_#Adlm, ha_GH, yi_001_#Hebr, to_TO_#Latn, ff_GW_#Latn, mua_CM_#Latn, nyn_UG, ms_MY, rn_BI_#Latn, ta_LK, tg_TJ, vun_TZ_#Latn, es_EC, mk_MK_#Cyrl, ff_CM_#Adlm, lg_UG, ff_NE_#Latn, cgg_UG, pcm_NG, en_BI, mi_NZ, ar_ER, es_EA, fr_SC, en_SL, ff_NG_#Latn, ff_NG_#Adlm, en_SH, eo_001, en_SI, vai_LR_#Vaii, rof_TZ_#Latn, ar_LB, ff_GN, eo_001_#Latn, hr_HR, rof_TZ, mn_MN, en_FM, fr_WF, ff_GM_#Latn, teo_UG_#Latn, asa_TZ_#Latn, bez_TZ, ff_GN_#Latn, sl_SI_#Latn, en_FK, bas_CM_#Latn, en_DG, pt_ST, ak_GH, es_419, ln_CD_#Latn, kkj_CM_#Latn, es_IC, ar_TN, bm_ML, jmc_TZ_#Latn, khq_ML, en_SB, rw_RW, shi_MA_#Tfng, ro_MD, uz_UZ, ia_001_#Latn, en_SC, en_UG, en_NZ, es_UY, ru_UA, sg_CF, en_BB, hr_BA, yo_NG_#Latn, lu_CD, ar_001, so_SO, lv_LV, sr_RS_#Cyrl, en_LS, ka_GE, sw_TZ, fr_RW, mg_MG, sr_RS_#Latn, ky_KG, tzm_MA_#Latn, ku_TR_#Latn, mfe_MU_#Latn, ky_KG_#Cyrl, qu_EC, ka_GE_#Geor, en_MS, kde_TZ_#Latn, sr_ME, en_ZM, fr_ML, ha_NG, os_GE, yi_001, en_GH, tzm_MA, ses_ML, rwk_TZ_#Latn, vai_LR_#Latn, sw_CD, ff_MR_#Adlm, en_VC, ses_ML_#Latn, en_150, ha_NE, en_KN, ro_RO, sr_ME_#Latn, ff_LR_#Latn, bas_CM, fr_MG, es_CL, sq_AL, ro_RO_#Latn, twq_NE, nmg_CM, en_MP, en_GD, sbp_TZ_#Latn, kde_TZ, ta_MY, si_LK_#Sinh, en_KI, twq_NE_#Latn, sq_AL_#Latn, ff_GH_#Adlm, fr_MF, en_SZ, rwk_TZ, kk_KZ, ar_PS, kkj_CM, es_GQ, en_SX, ru_KZ, ko_KP, nl_SR, bem_ZM_#Latn, cgg_UG_#Latn, nl_BQ, ee_GH_#Latn, ff_GN_#Adlm, uz_UZ_#Cyrl, asa_TZ, fr_SN, fr_MA, ff_GM_#Adlm, fr_BL, mgo_CM, nmg_CM_#Latn, tg_TJ_#Cyrl, en_SS, shi_MA, en_MG, fr_BI, naq_NA_#Latn, fr_BJ, vai_LR, bs_BA_#Cyrl, khq_ML_#Latn, mn_MN_#Cyrl, wo_SN, ha_NG_#Latn, fr_HT, nl_SX, fr_CG, ms_MY_#Latn, zgh_MA_#Tfng, nyn_UG_#Latn, en_VU, to_TO, ff_SL_#Latn, xog_UG_#Latn, ff_SN_#Adlm, vi_VN_#Latn, jgo_CM_#Latn, zgh_MA, uk_UA, lg_UG_#Latn, en_NF, sr_XK_#Cyrl, ar_SS, nl_AW, en_AI, xog_UG, en_CM, ru_MD, ff_SN_#Latn, en_TO, ff_SL_#Adlm, en_PG, fr_CF, pt_TL, en_ER, sr_BA, be_BY_#Cyrl, fr_TG, sr_XK_#Latn, ig_NG, fr_GN, en_CK, ar_MA, fr_TD, bem_ZM, ewo_CM_#Latn, ewo_CM, fr_CD, rn_BI, en_NA, mgo_CM_#Latn, lag_TZ, qu_BO, kea_CV_#Latn, sq_XK, mi_NZ_#Latn, en_KY, si_LK, ar_SO, fr_MU, en_TK, mua_CM, pt_GW, ee_TG, ln_AO, be_BY, pt_CV, ru_BY, ee_GH, kk_KZ_#Cyrl, yo_BJ, fr_KM, es_AR, en_MY, sbp_TZ, hy_AM, en_GM, ksb_TZ_#Latn, pt_AO, en_001, dua_CM_#Latn, lag_TZ_#Latn, ru_KG, fr_MR, ksf_CM_#Latn, ff_BF_#Latn, en_MW, naq_NA, en_IO, en_CC, az_AZ_#Cyrl, shi_MA_#Latn, es_CU, ig_NG_#Latn, yav_CM, da_GL, wo_SN_#Latn, es_CR, fr_GA, en_MU, az_AZ_#Latn, dyo_SN_#Latn, en_VG, en_TC, af_NA, mas_TZ, ms_BN],
WeekFields[SATURDAY,1]=[ar_EG, ps_AF_#Arab, ar_SY, uz_AF, lrc_IR, ar_OM, lrc_IQ, ar_DZ, fa_IR_#Arab, fr_DJ, so_DJ, lrc_IR_#Arab, ar_AE, mzn_IR, en_SD, uz_AF_#Arab, ar_IQ, ar_KW, ar_JO, fr_DZ, fa_AF, ckb_IQ_#Arab, ar_SD, fa_IR, kab_DZ_#Latn, kab_DZ, ps_AF, mzn_IR_#Arab, en_AE, ar_BH, ar_QA, ar_EG_#Arab, ckb_IQ, ckb_IR, ar_LY, fr_SY, ar_DJ],
WeekFields[MONDAY,4]=[se_NO_#Latn, dsb_DE, lb_LU_#Latn, se_NO, pl_PL, nds_DE, nb_SJ, lb_LU, dsb_DE_#Latn, is_IS_#Latn, no_NO_NY, pl_PL_#Latn, nds_DE_#Latn, tt_RU, fur_IT_#Latn, ast_ES_#Latn, en_JE, da_DK_#Latn, en_AT, gd_GB, wae_CH_#Latn, no_NO_#Latn, smn_FI_#Latn, en_NL, gsw_FR, hu_HU, bg_BG_#Cyrl, et_EE, fy_NL, de_IT, de_CH, nl_NL, pt_CH, gv_IM, kw_GB_#Latn, fi_FI_#Latn, fr_BE, nb_NO, it_SM, fi_FI, ca_FR, de_BE, de_DE_#Latn, fr_FR_#Latn, ksh_DE, ru_RU, ga_IE, rm_CH_#Latn, fr_LU, ga_GB, no_NO, de_LU, de_DE, nn_NO_#Latn, en_DK, lt_LT, ksh_DE_#Latn, da_DK, en_BE, tt_RU_#Cyrl, ga_IE_#Latn, is_IS, en_SE, gsw_LI, kw_GB, en_DE, en_FI, sah_RU_#Cyrl, en_FJ, de_LI, smn_FI, de_AT, ce_RU, os_RU, nl_NL_#Latn, gl_ES_#Latn, en_GG, sv_SE, el_GR_#Grek, it_VA, es_ES, bg_BG, hsb_DE, eu_ES_#Latn, sv_SE_#Latn, sv_FI, en_IE, fo_FO_#Latn, en_GB, fr_MC, ce_RU_#Cyrl, gsw_CH, pt_LU, hsb_DE_#Latn, br_FR_#Latn, es_ES_#Latn, cy_GB_#Latn, fr_FR, sah_RU, sk_SK, ru_RU_#Cyrl, gv_IM_#Latn, nds_NL, fr_RE, fr_GP, fr_CH, nb_NO_#Latn, fy_NL_#Latn, cs_CZ, ca_ES, hu_HU_#Latn, rm_CH, et_EE_#Latn, gd_GB_#Latn, se_FI, lt_LT_#Latn, ca_IT, ca_AD, it_CH, it_IT, nn_NO, se_SE, it_IT_#Latn, wae_CH, fo_DK, en_CH, fo_FO, ast_ES, fr_MQ, fur_IT, fr_GF, sk_SK_#Latn, eu_ES, el_GR, ca_ES_VALENCIA, gl_ES, en_IM, gsw_CH_#Latn, en_GI, ca_ES_#Latn, nl_BE, cy_GB, sv_AX, cs_CZ_#Latn, br_FR],
WeekFields[SUNDAY,1]=[, he, th_TH_#Thai, nds, ti_ET, ta_SG, lv, zh_SG_#Hans, en_JM, kkj, sd__#Arab, dz_BT, mni, yi, cs, el, af, smn, dsb, khq, ne_IN, es_US, sa, en_US_POSIX, pt_MO, zh__#Hans, so_KE, gu_IN_#Gujr, teo, eu, es_DO, ru, az, su__#Latn, fa, nd, kk, hy, en_AU, ksb, luo, lb, su, no, ar_IL, mgh, or_IN, az__#Latn, ta, lag, luo_KE_#Latn, bo, om_KE, en_AS, zh_TW, sd_IN, kln, mai, pt_MZ, my_MM_#Mymr, gl, sr__#Cyrl, yue_CN_#Hans, ff__#Adlm, kn_IN, ga, qu, en_PR, mua, jv, ps, sn, km, zgh, es, jgo, gsw, pa_IN_#Guru, ur_PK_#Arab, ceb, bn_BD_#Beng, ne_NP_#Deva, te, sl, mr_IN, ha, guz_KE_#Latn, es_HN, sbp, sw, nmg, pt_BR_#Latn, vai__#Vaii, gu, lo, zh_HK_#Hans, bs__#Latn, os, am, ki_KE, en_PK, zh_CN, rw, brx_IN, en_TT, dav, ses, xh_ZA, es_VE, mer_KE, mg, mr, seh, mgo, en_US, pa__#Guru, sa_IN_#Deva, gu_IN, ast, mt_MT_#Latn, yue__#Hans, ccp_BD_#Cakm, ks__#Arab, af_ZA_#Latn, ti_ET_#Ethi, am_ET_#Ethi, cgg, zh_MO, ksf, cy, ceb_PH, sq, fr, qu_PE, de, zu_ZA, su_ID_#Latn, lg, en_DM, sd, he_IL_#Hebr, yue_CN, en_WS, so, kab, nus, sn_ZW, th_TH_TH_#u-nu-thai, hi, zh_MO_#Hant, vai, sd_PK_#Arab, mi, mt, yav, kam, ro, ps_PK, ee, en_UM, lo_LA, chr, af_ZA, doi, es_BZ, as, it, ks_IN, my_MM, ur_PK, ii, naq, en_SG, kln_KE, tzm, fur, om, mai_IN_#Deva, ja_JP_JP_#u-ca-japanese, es_SV, pt_BR, mni_IN_#Beng, ml_IN, hr, lt, ccp, zh_CN_#Hans, en, guz_KE, ccp_BD, ca, pa_PK, ug_CN, ki_KE_#Latn, es_BR, bo_CN_#Tibt, chr_US, nyn, mk, sat, pa__#Arab, bs, fy, th, dav_KE, dje, mas_KE, mni_IN, ckb, bem, da, wae, ig, en_HK, brx_IN_#Deva, mer_KE_#Latn, en_US_#Latn, ki, nb, kok, ewo, nn, bg, kea, zu, am_ET, bo_CN, hsb, kok_IN_#Deva, sat_IN, pcm, sah, mer, br, ar_SA, fil_PH_#Latn, sk, om_ET_#Latn, ml, en_MT, en_IL, sv, kn_IN_#Knda, lkt_US, sd__#Deva, ku, fil_PH, es_PH, es_CO, agq, ebu, es_GT, nd_ZW_#Latn, mn, kam_KE, en_MO, ja_JP_#Jpan, wo, shi__#Tfng, en_BZ, lkt_US_#Latn, ta_IN_#Taml, az__#Cyrl, tk, shi__#Latn, en_BW, he_IL, nd_ZW, luy_KE_#Latn, mni__#Beng, ne, zh_SG, om_ET, lo_LA_#Laoo, ja_JP, kam_KE_#Latn, my, ka, ko_KR_#Kore, ms_ID, shi, kl, sa_IN, yue_HK, id, zh, es_PE, mgh_MZ, saq, zh_HK_#Hant, sat_IN_#Olck, es_PA, bez, kw, vai__#Latn, ksh, ur_IN, ln, luy_KE, pt, mgh_MZ_#Latn, ar_YE, to, et, rof, en_BS, be, gv, kln_KE_#Latn, dua, hi_IN_#Deva, guz, en_KE, mfe, ja, or, brx, mai_IN, ko_KR, es_MX, zu_ZA_#Latn, doi_IN, fi, uz, bs__#Cyrl, sr__#Latn, bo_IN, rm, bn, kn, nnh, bn_BD, en_ZA, pa_IN, en_MH, zh__#Hant, jv_ID_#Latn, ky, mas, xh_ZA_#Latn, dav_KE_#Latn, xh, te_IN, mas_KE_#Latn, lrc, ce, mt_MT, ko, ml_IN_#Mlym, ak, kde, dz, ia, seh_MZ, su_ID, ii_CN, pa_PK_#Arab, bn_IN, pa, rwk, rn, tg, hu, ceb_PH_#Latn, twq, bm, en_GU, tr, es_PY, kok_IN, dz_BT_#Tibt, en_PH, zh_MO_#Hans, sat__#Olck, ff, haw_US_#Latn, en_AG, ebu_KE, xog, ms, ug, qu_PE_#Latn, id_ID, teo_KE, haw_US, vi, fr_CA, dyo, luo_KE, eo, en_ZW, pl, ur, uz__#Arab, saq_KE, se, sn_ZW_#Latn, ms_SG, yue__#Hant, km_KH_#Khmr, luy, uk, es_PR, mzn, tt, ug_CN_#Arab, hi_IN, saq_KE_#Latn, asa, ff__#Latn, doi_IN_#Deva, ebu_KE_#Latn, uz__#Cyrl, fil, yue_HK_#Hant, fo, ne_NP, ta_IN, lkt, id_ID_#Latn, is, te_IN_#Telu, si, jv_ID, ks, zh_TW_#Hant, as_IN, zh_HK, sw_KE, th_TH, as_IN_#Beng, jmc, yue, ar, en_VI, haw, bas, uz__#Latn, sg, km_KH, nl, ks_IN_#Arab, sd_IN_#Deva, mr_IN_#Deva, sr, seh_MZ_#Latn, en_CA, gd, chr_US_#Cher, or_IN_#Orya, so_ET, vun, en_IN, lu, yo, es_NI, ii_CN_#Yiii, sd_PK, ti, ccp_IN],
WeekFields[SUNDAY,4]=[pt_PT]}
```

---

Originally published at [https://stepniewski.tech](https://stepniewski.tech/L/32YMEgeZ8).

All code examples from this post you will find at my [github](https://stepniewski.tech/L/EuCSN418)