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:
-
YYYY
is "week-based-year" -
yyyy
is "year-of-era"
What means "week-based-year"? According to WeekFields
Java documentation
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:
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.
All the following assertions are true.
@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
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.
@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.
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:
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.
All code examples from this post you will find at my github
Top comments (0)