ใช้ C ++ 20 chrono วิธีคำนวณข้อเท็จจริงต่าง ๆ เกี่ยวกับวันที่


19

https://www.timeanddate.com/date/weekday.htmlคำนวณข้อเท็จจริงต่าง ๆ เกี่ยวกับวันของปีตัวอย่างเช่น:

https://i.stack.imgur.com/WPWuO.png

ให้วันที่โดยพลการตัวเลขเหล่านี้สามารถคำนวณได้ด้วยข้อกำหนดC ++ 20 chronoอย่างไร


2
"... และเราทุกคนรู้เมื่อ ISO 1 สัปดาห์ใช่มั้ย ... " - "ไม่ แต่ฉันมีห้องสมุด" ... :-) - Bravo Howard!
Ted Lyngmo

ภาพที่นำมาจากstackoverflow.com/q/59391132/560648 (ตอนนี้ถูกลบ) น่าเสียดายที่มันถูกลบไปเพราะนี่น่าจะเป็นคำตอบสำหรับคำถามนั้น
การแข่งขัน Lightness ใน Orbit

แก้ไข. ฉันโหวตให้เปิดใหม่
Howard Hinnant

คำตอบ:


22

นี้เป็นอย่างน่าทึ่งง่ายด้วยC ++ 20 สเปคโครโน coutด้านล่างนี้ผมแสดงฟังก์ชั่นซึ่งปัจจัยการผลิตวันโดยพลการและพิมพ์ข้อมูลนี้ไปยัง แม้ว่าในขณะที่เขียนนี้ที่สเปคโครโน C ++ 20ยังไม่ได้รับการจัดส่งสินค้าก็จะประมาณโดยฟรีห้องสมุดเปิดแหล่งที่มา ดังนั้นคุณสามารถทดลองกับมันได้ในวันนี้และรวมไว้ในแอปพลิเคชันการจัดส่งตราบใดที่คุณใช้ C ++ 11 หรือใหม่กว่า

คำตอบนี้จะอยู่ในรูปแบบของฟังก์ชั่น:

void info(std::chrono::sys_days sd);

sys_daysเป็นวันที่มีความแม่นยำtime_pointในsystem_clockครอบครัว ซึ่งหมายความว่ามันเป็นเพียงการนับวันตั้งแต่ 1970-01-01 00:00:00 UTC นามแฝงประเภทsys_daysนั้นใหม่พร้อมด้วย C ++ 20 แต่ประเภทพื้นฐานมีให้บริการตั้งแต่ C ++ 11 ( time_point<system_clock, duration<int, ratio<86400>>>) ถ้าคุณใช้โอเพนซอร์ส C ++ 20 ห้องสมุดภาพตัวอย่าง , อยู่ในsys_daysnamespace date

รหัสด้านล่างจะถือว่าเป็นฟังก์ชันในเครื่อง:

using namespace std;
using namespace std::chrono;

เพื่อลดการฟุ่มเฟื่อย หากคุณกำลังทดลองกับไลบรารีตัวอย่าง C ++ 20 แบบโอเพ่นซอร์สให้ถือว่า:

using namespace date;

หัวเรื่อง

ในการแสดงผลสองบรรทัดแรกนั้นง่าย:

cout << format("{:%d %B %Y is a %A}\n", sd)
     << "\nAdditional facts\n";

เพียงใช้วันที่sdและใช้งานformatร่วมกับstrftime/ put_timeธงที่คุ้นเคยเพื่อพิมพ์วันที่และข้อความ โอเพนซอร์ส C ++ 20 ห้องสมุดภาพตัวอย่างยังไม่รวมห้องสมุด fmt"%d %B %Y is a %A\n"และเพื่อให้ใช้สตริงรูปแบบการเปลี่ยนแปลงเล็กน้อย

สิ่งนี้จะเอาท์พุต (ตัวอย่าง):

26 December 2019 is a Thursday

Additional facts

ผลลัพธ์กลางทั่วไปที่คำนวณได้หนึ่งครั้ง

ส่วนของฟังก์ชั่นนี้เขียนครั้งสุดท้ายเพราะยังไม่ทราบว่าการคำนวณจะต้องใช้หลายครั้ง แต่เมื่อคุณรู้แล้วนี่คือวิธีการคำนวณ:

year_month_day ymd = sd;
auto y = ymd.year();
auto m = ymd.month();
weekday wd{sd};
sys_days NewYears = y/1/1;
sys_days LastDayOfYear = y/12/31;

เราจะต้องการฟิลด์ปีและเดือนของsdและweekday(วันของสัปดาห์) มันมีประสิทธิภาพในการคำนวณพวกเขาครั้งเดียวและสำหรับทั้งหมดในลักษณะนี้ นอกจากนี้เรายังต้องการ (หลายครั้ง) ในวันแรกและวันสุดท้ายของปีปัจจุบัน มันยากที่จะบอก ณ จุดนี้ แต่มันมีประสิทธิภาพในการจัดเก็บค่าเหล่านี้เป็นประเภทsys_daysเนื่องจากการใช้งานครั้งต่อไปของพวกเขาจะใช้เฉพาะกับเลขคณิตแบบมุ่งเน้นรายวันซึ่งsys_daysมีประสิทธิภาพมากที่ (ความเร็วระดับนาโนวินาที)

ข้อเท็จจริง 1: จำนวนวันของปีและจำนวนวันที่เหลือในปี

auto dn = sd - NewYears + days{1};
auto dl = LastDayOfYear - sd;
cout << "* It is day number " << dn/days{1} << " of the year, "
     << dl/days{1} << " days left.\n";

พิมพ์นี้ออกมาจำนวนวันของปีกับ 1 มกราคมเป็นวันที่ 1 sdแล้วยังพิมพ์ออกมาจำนวนวันที่เหลือในปีนี้ไม่รวม การคำนวณเพื่อทำสิ่งนี้เป็นเรื่องเล็กน้อย การหารผลลัพธ์แต่ละรายการด้วยdays{1}วิธีการแยกจำนวนวันในdnและdlเป็นประเภทอินทิกรัลสำหรับวัตถุประสงค์ในการจัดรูปแบบ

ข้อเท็จจริงที่ 2: จำนวนวันทำงานนี้และจำนวนวันทำงานทั้งหมดในปี

sys_days first_wd = y/1/wd[1];
sys_days last_wd = y/12/wd[last];
auto total_wd = (last_wd - first_wd)/weeks{1} + 1;
auto n_wd = (sd - first_wd)/weeks{1} + 1;
cout << format("* It is {:%A} number ", wd) << n_wd << " out of "
     << total_wd << format(" in {:%Y}.\n}", y);

wdเป็นวันในสัปดาห์ (วันจันทร์ถึงวันอาทิตย์) คำนวณที่ด้านบนของบทความนี้ เพื่อดำเนินการคำนวณนี้ครั้งแรกที่เราต้องทราบวันแรกและสุดท้าย'ในปี wd เป็นคนแรกในเดือนมกราคมและเป็นคนสุดท้ายในเดือนธันวาคมyy/1/wd[1]wdy/12/wd[last]wd

จำนวนทั้งหมดของwdปีเป็นเพียงจำนวนสัปดาห์ระหว่างสองวันนี้ (บวก 1) นิพจน์ย่อยlast_wd - first_wdคือจำนวนวันระหว่างวันที่สองวัน การหารผลลัพธ์นี้ด้วย 1 สัปดาห์ส่งผลให้มีประเภทหนึ่งที่ถือจำนวนสัปดาห์ระหว่างวันที่สองวัน

จำนวนสัปดาห์จะทำแบบเดียวกับจำนวนรวมของสัปดาห์ยกเว้นหนึ่งเริ่มต้นด้วยวันที่ปัจจุบันแทนของสุดท้ายของปี:wdsd - first_wd

ข้อเท็จจริงที่ 3: จำนวนวันทำงานนี้และจำนวนวันทำงานทั้งหมดในเดือน

first_wd = y/m/wd[1];
last_wd = y/m/wd[last];
total_wd = (last_wd - first_wd)/weeks{1} + 1;
n_wd = (sd - first_wd)/weeks{1} + 1;
cout << format("* It is {:%A} number }", wd) << n_wd << " out of "
     << total_wd << format(" in {:%B %Y}.\n", y/m);

สิ่งนี้ทำงานได้เหมือน Fact 2 ยกเว้นว่าเราเริ่มต้นด้วยคู่แรกและคู่สุดท้ายwdของคู่เดือนปีy/mแทนที่จะเป็นทั้งปี

ความจริง 4: จำนวนวันในปี

auto total_days = LastDayOfYear - NewYears + days{1};
cout << format("* Year {:%Y} has ", y) << total_days/days{1} << " days.\n";

รหัสสวยมากพูดสำหรับตัวเอง

ข้อเท็จจริง 5 จำนวนวันในเดือน

total_days = sys_days{y/m/last} - sys_days{y/m/1} + days{1};
cout << format("* {:%B %Y} has ", y/m) << total_days/days{1} << " days.\n";

การแสดงออกy/m/lastเป็นวันสุดท้ายของคู่เดือนปีy/mและแน่นอนy/m/1เป็นวันแรกของเดือน ทั้งสองถูกแปลงเป็นเพื่อsys_daysให้สามารถลบออกเพื่อรับจำนวนวันระหว่างพวกเขา เพิ่ม 1 สำหรับการนับ 1 รายการ

ใช้

info สามารถใช้ดังนี้:

info(December/26/2019);

หรือเช่นนี้

info(floor<days>(system_clock::now()));

นี่คือตัวอย่างเอาต์พุต:

26 December 2019 is a Thursday

Additional facts
* It is day number 360 of the year, 5 days left.
* It is Thursday number 52 out of 52 in 2019.
* It is Thursday number 4 out of 4 in December 2019.
* Year 2019 has 365 days.
* December 2019 has 31 days.

แก้ไข

สำหรับผู้ที่ไม่ชอบ "ไวยากรณ์ทั่วไป" จะมี "ผู้สร้างไวยากรณ์" ที่สามารถใช้แทนได้

ตัวอย่างเช่น:

sys_days NewYears = y/1/1;
sys_days first_wd = y/1/wd[1];
sys_days last_wd = y/12/wd[last];

สามารถถูกแทนที่ด้วย:

sys_days NewYears = year_month_day{y, month{1}, day{1}};
sys_days first_wd = year_month_weekday{y, month{1}, weekday_indexed{wd, 1}};
sys_days last_wd = year_month_weekday_last{y, month{12}, weekday_last{wd}};

5
การใช้งานในทางที่ผิดของผู้ปฏิบัติงานในแผนกนี้ยิ่งแย่กว่าการใช้งานบิตเก่าของผู้ใช้งานในทางที่ผิด มันทำให้ฉันเศร้า :(
เดฟ

2
ในบันทึกที่ร้ายแรงกว่านี้ฉันขอแนะนำให้คุณย้ายตัวแปรที่คำนวณไว้ล่วงหน้าบางส่วนไปยังส่วนที่ใช้งานได้หรือไม่ เป็นเรื่องที่น่าอึดอัดใจที่ต้องติดตามเมื่อต้องเลื่อนขึ้นและลงเพื่อดูว่าค่ามาจากไหนและสร้างอย่างไร และคุณสามารถขจัดความยุ่งเหยิงของคุณในแต่ละวันได้โดยทำแผนกแรกเหมือนที่คุณทำมาหลายสัปดาห์
เดฟ

1
ไม่เห็นด้วยอย่างสมบูรณ์ ดูดีเข้าใจง่ายและสะดุดตาอ่านง่ายกว่าเวอร์ชั่น verbose มากขึ้น
Cássio Renan

@ CássioRenanอาจเป็นได้ แต่โปรดจำไว้ว่าการใช้ไวยากรณ์ในทางที่ผิดมักจะมาพร้อมกับพฤติกรรมที่ไม่คาดคิด ด้วยการเลื่อนบิตดังกล่าวข้างต้นเช่นสังเกตพฤติกรรมของstd::cout << "a*b = " << a*b << "; a^b = " << a^b << '\n';(ซึ่งก็คือโชคดีที่เกือบทุกครั้งที่รวบรวมเวลา แต่ก็ยังเป็นเรื่องน่ารำคาญ) ดังนั้นฉันจึงต้องระมัดระวังเมื่อใช้การละเมิดผู้ปฏิบัติงานในแผนกใหม่นี้
Ruslan

@Ruslan ข้อควรระวังจะรับประกันกับห้องสมุดใหม่ใด ๆ เสมอ นั่นเป็นเหตุผลที่สิ่งนี้ได้รับการทดสอบอย่างอิสระและเปิดเผยต่อสาธารณชนตั้งแต่ปี 2558 ผลตอบรับจากลูกค้าได้ถูกนำกลับมารวมไว้ในการออกแบบ มันไม่ได้ถูกเสนอสำหรับการสร้างมาตรฐานจนกว่าจะมีรากฐานที่แข็งแกร่งของประสบการณ์ภาคสนามที่เป็นบวก โดยเฉพาะอย่างยิ่งการใช้ตัวดำเนินการได้รับการออกแบบโดยคำนึงถึงความสำคัญของผู้ปฏิบัติงานการทดสอบภาคสนามอย่างกว้างขวางและมาพร้อมกับ "constructor API" ที่เทียบเท่า ดูstar-history.t9t.io/#HowardHinnant/date&google/cctzและyoutube.com/watch?v=tzyGjOm8AKo
Howard Hinnant
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.