"তারিখ ও সময় তো ছোট বাচ্চাদেরও শেখানো হয়। এটা আবার কত কঠিন হতে পারে?"
বহু প্রোগ্রামার এই ভুলটাই করেছেন, আর তার পরের অভিজ্ঞতা তাদের স্বাস্থ্য ও সুখে খারাপ প্রভাব ফেলে।
তারিখ ও সময় নিয়ে একটুও জটিল প্রোগ্রামিং করছেন যিনি, তার অন্তত সম্ভাব্য সমস্যাগুলো বুঝতে ও কমাতে প্রস্তুত থাকা উচিত।
এই ডকুমেন্টে R কীভাবে তারিখ ও সময় সামলায় তার একটি সংক্ষিপ্ত পরিচয় দেওয়ার চেষ্টা করা হয়েছে, তবে আরও বিস্তারিত জানতে পুরো ডকুমেন্টেশন দেখতে হবে।
ডেটা সায়েন্সের ভাষা হিসেবে R-এ তারিখ ও সময়ের আলাদা গুরুত্ব আছে। বেশিরভাগ ডেটা আসে টাইম সিরিজের আকারে: কোনো রোগের প্রাদুর্ভাবের সময় প্রতিদিনের রোগীর সংখ্যা, সরকারের রিপোর্ট করা মাসিক কর্মসংস্থানের পরিসংখ্যান, বাসা বাঁধার কলোনিতে পাখির সংখ্যা, বড় অর্থনীতির বার্ষিক জিডিপি, আর এরকম অসংখ্য উদাহরণ।
তার মানে কি R ডেটা ও সময় সামলানো সহজ করে দেয়?
দুঃখজনকভাবে, না!
Base R-এর তারিখ ও সময়ের সুবিধার সুনাম ভালো নয়, আর এর বেশিরভাগই এখন কম ব্যবহৃত হয়।
lubridate প্যাকেজ অনেক বড় একটা উন্নতি, তবে তৈরি থাকুন একটি বড় ও জটিল লাইব্রেরির জন্য, যা বড় ও জটিল ধরনের সমস্যা সামলানোর জন্য বানানো।
শুরুতে আলাদা থাকলেও, lubridate এখন tidyverse সংগ্রহে যুক্ত করা হয়েছে।
এটি library(lubridate) বা library(tidyverse) দিয়ে লোড করুন।
lubridate নিয়ে কাজ করতে সাহায্য করে এমন কিছু রিসোর্স:
একটি date হলো শুধু একটি দিন, কোনো সময়ের তথ্য ছাড়া।
ভেতরে ভেতরে, তারিখ সংরক্ষিত হয় 1970-01-01 থেকে দিনের (চিহ্নযুক্ত) সংখ্যা হিসেবে।
today()
#> [1] "2026-04-22"
একটি datetime তারিখ, সময় ও টাইমজোনের তথ্য একসাথে ধরে।
ভেতরে ভেতরে, এটি সংরক্ষিত হয় ১৯৭০ সালের শুরু থেকে UTC টাইমজোনে সেকেন্ডের সংখ্যা হিসেবে।
now()
#> [1] "2026-04-22 15:37:11 MST" # mid-afternoon in Arizona
বিস্ময়ের ব্যাপার হলো, শুধু সময়ের মানের জন্য R-এর কোনো অভ্যন্তরীণ উপস্থাপনা নেই।
date ও datetime মান নিয়ে কাজ করাতারিখ/সময় নিয়ে সবচেয়ে বেশি যা দরকার হয়:
today() দিয়ে) এবং/অথবা ডেটটাইম (now() দিয়ে) পাওয়া।তারিখ ও সময় লেখার অনেক রকম উপায় আছে, যা সাধারণত সংস্কৃতিভেদে আলাদা হয়। "7/6/23"-এর মতো সব সংখ্যার তারিখ দ্ব্যর্থক ও বিভ্রান্তিকর, আর বহুজাতিক প্রতিষ্ঠানে অনেক দামি ভুলের কারণ হয়েছে (লেখক এটি নিজের তিক্ত অভিজ্ঞতা থেকে বলছেন)।
আন্তর্জাতিক মানদণ্ড (y-m-d) নির্ধারিত হয়েছে ISO 8601-এ, যার দুটি প্রধান সুবিধা আছে:
তবে, ডেটা সায়েন্স মানে যা ডেটা পাবেন তা নিয়েই কাজ করা, আর অন্যরা কীভাবে ফরম্যাট করে তা নিয়ে অভিযোগ করা প্রায়ই অর্থহীন।
Base R-এ কনভার্সন ফাংশন আছে, যেগুলো একটি ফরম্যাট স্ট্রিং নেয় (ডিফল্ট ISO 8601)।
as.Date("2026-04-22")
#> [1] "2026-04-22"
as.Date("4/22/26", format = "%m/%d/%y")
#> [1] "2026-04-22"
as.POSIXct("2026-04-22 15:30:09 MST")
#> [1] "2026-04-22 15:30:09 MST"
ফরম্যাট স্ট্রিংগুলো কয়েক দশক পুরনো strptime() ফাংশনের মতো, যা অনেক ভাষায় আছে।
এই সিনট্যাক্সের সাথে পরিচিত প্রোগ্রামাররা চাইলে lubridate-এর fast_strptime() ফাংশন ব্যবহার করতে পারেন।
আরও সহায়কভাবে, lubridate অনেক ধরনের হেল্পার ফাংশন দেয়, যাদের নাম ফিল্ডের ক্রম বোঝায়।
একবার ফাংশনগুলো জেনে গেলে কোন মানগুলো খুঁজতে হবে আর কোন ক্রমে, পার্সিং হয়ে যায় চমকপ্রদভাবে নমনীয়।
mdy("4/22/26")
#> [1] "2026-04-22"
mdy("April 22nd 26")
#> [1] "2026-04-22"
mdy("Wednesday, April 22nd 2026")
#> [1] "2026-04-22"
mdy_hm("Wednesday, April 22nd 2026, 4:14 pm")
#> [1] "2026-04-22 16:14:00 UTC" # defaults to your OS timezone
যখন আপনার প্রোগ্রামে একটি তারিখ/ডেটটাইমের অংশগুলো আলাদা আলাদা সংখ্যা হিসেবে থাকে, তখন সেগুলো make_date() বা make_datetime() দিয়ে জোড়া লাগানো যায়।
যেসব অংশ দেওয়া যায় সেগুলো এখানে, তাদের ক্রম ও ডিফল্ট মানসহ:
make_datetime(
year = 1970L,
month = 1L,
day = 1L,
hour = 0L,
min = 0L,
sec = 0,
tz = "UTC"
)
#> [1] "1970-01-01 UTC"
make_datetime(2026, 4, 22, 17)
#> [1] "2026-04-22 17:00:00 UTC"
স্বভাবতই, make_date() শুধু year, month আর day আর্গুমেন্ট নেয়।
R কনসোল স্বয়ংক্রিয়ভাবে তারিখ/ডেটটাইমকে একটি স্ট্রিং উপস্থাপনায় দেখায়। এটি সাধারণত সুবিধাজনক, তবে মাঝে মাঝে বিভ্রান্তিকর।
ভেতরে ভেতরে, তারিখ ও ডেটটাইম হলো সংখ্যা (সাথে কয়েকটি অ্যাট্রিবিউট), স্ট্রিং নয়। যখন একটি স্ট্রিং প্রত্যাশিত, তখন তারিখ(সময়) রিটার্ন করলে সমস্যা হবে, তাই কনভার্সনটি কীভাবে করতে হয় তা জানা দরকার।
সহজ একটি উপায় হলো Base R-এর as.character() ব্যবহার করা, যা ISO 8601 ফরম্যাটে একটি স্ট্রিং আউটপুট দেয়।
ডেটটাইমের জন্য format_ISO8601() ফাংশনও আছে, যাতে ইচ্ছেমতো নির্ভুলতায় রাউন্ড করার অপশন আছে।
today() |> as.character() # a date
#> [1] "2026-04-23"
now() |> as.character() # a datetime
#> [1] "2026-04-23 09:07:09.779319"
now() |> format_ISO8601() # defaults to nearest second
#> [1] "2026-04-23T09:07:38"
প্রোগ্রামার নন এমন কম মানুষই ISO 8601 আউটপুট দেখতে চান, তাই সংস্কৃতিভেদে তারিখ/সময়ের ফরম্যাট সামলানো অপরিহার্য।
Base R দেয় format() ফাংশন, যা অনেক অন্য ভাষায় থাকা সুপ্রাচীন strftime() ফাংশনের মতো ফরম্যাট স্ট্রিং ব্যবহার করে।
format(today(), "%A, %d %B %Y")
#> [1] "Thursday, 23 April 2026"
Base R-এর এই পদ্ধতি দেখতে কুৎসিত, কিন্তু নির্ভরযোগ্য। এগুলো আরও কমন কিছু কোড:
| কোড | ফলাফল |
|---|---|
| %d | মাসের দিনের সংখ্যা (5, 17, 28, ইত্যাদি) |
| %a | সংক্ষিপ্ত বার (Mon, Tue, Wed, ইত্যাদি) |
| %A | পূর্ণ বার (Monday, Tuesday, ইত্যাদি) |
| %w | বারের সংখ্যা (0-6, Sunday হলো 0) |
| %u | বারের সংখ্যা (1-7, Monday হলো 1) |
| %b | সংক্ষিপ্ত মাস (Jan, Feb, ইত্যাদি) |
| %B | পূর্ণ মাস (January, February, ইত্যাদি) |
| %y | দুই অঙ্কের বছর (যেমন 89) |
| %Y | চার অঙ্কের বছর (যেমন 1989) |
| %h | ঘণ্টা (২৪-ঘণ্টার ঘড়ি) |
| %m | মিনিট |
| %s | সেকেন্ড |
| %z | UTC থেকে অফসেট |
| %Z | টাইমজোন (ক্যারেক্টার) |
Lubridate আরও ব্যবহারবান্ধব একটি বিকল্প দেওয়ার লক্ষ্য নিয়েছে stamp() ফাংশন দিয়ে, আর আরও সীমিত stamp_date() ও stamp_time() দিয়ে।
এগুলো একটি টেমপ্লেট স্ট্রিং নেয়, যা কাঙ্ক্ষিত আউটপুটের একটি উদাহরণ, আর সেই ফরম্যাট তৈরি করার জন্য একটি কাস্টম ফাংশন বানায়।
Lubridate ফরম্যাটটি অনুমান করার চেষ্টা করে, কিন্তু প্রায়ই দ্ব্যর্থতার মুখে পড়ে ভুল ফলাফল দেয়।
দ্ব্যর্থতা দূর করতে প্রস্তুত থাকুন ফরম্যাট স্ট্রিং দিতে (যেমন "dmy")।
Exercism-এর জন্য, locale = "C" নির্দিষ্ট করাও কাজে লাগতে পারে, যাতে আপনার সিস্টেম লোকেল বাইপাস করে ইংরেজিতে কাজ করা যায় (যেমনটা tests আশা করে)।
লোকেল জিনিসটা জটিল!
sf <- stamp("Saturday, June 27, 2026 at 14:45", locale = "C")
#> Multiple formats matched: "%A, %B %d, %Y at %H:%M"(1), "Saturday, %Om %d, %Y at %H:%M"(1), "Saturday, %B %d, %Y at %H:%M"(1), "%A, %Om %d, %Y at %H:%M"(0)
#> Using: "%A, %B %d, %Y at %H:%M"
sf(make_datetime(2023, 3, 29, 15, 0, 0))
#> [1] "Wednesday, March 29, 2023 at 15:00"
Lubridate একটি তারিখ বা ডেটটাইমের এলিমেন্ট পেতে বা সেট করতে অনেক ফাংশন দেয়: স্পষ্ট যেগুলো যেমন year() ও second(), আবার আরও বিশেষায়িত যেগুলো যেমন quarter() ও epiweek()।
এমন সব ফাংশন একটি মান পেতে ব্যবহার করা যায়, আর বেশিরভাগই মান সেট করতেও ব্যবহার করা যায়।
dt <- now()
dt
#> [1] "2026-04-23 15:47:11 MST"
# get
month(dt)
#> [1] 4
# set
month(dt) <- 11
dt
#> [1] "2026-11-23 15:47:11 MST"
wday() (সপ্তাহের বার) আর month()-এর ক্ষেত্রে ডিফল্ট হলো ইন্টিজার আউটপুট, তবে:
labels = TRUE সেট করলে বর্তমান লোকেলের সাথে মেলে এমন একটি সংক্ষিপ্ত ক্যারেক্টার স্ট্রিং রিটার্ন করে।abbr = FALSE সেট করলেও পূর্ণ নাম রিটার্ন করে।d <- today()
d
#> [1] "2026-04-23"
wday(d)
#> [1] 5
wday(d, label = TRUE)
#> [1] Thu
#> Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
wday(d, label = TRUE, abbr = FALSE)
#> [1] Thursday
#> Levels: Sunday < Monday < Tuesday < Wednesday < Thursday < Friday < Saturday
লেবেলগুলো হলো categorical variables, যা আরেকটি কনসেপ্টের বিষয় হবে।
মানগুলো তালিকাভুক্ত Levels-এর মধ্যে সীমাবদ্ধ থাকে, যা সাজানোর ক্রমও দেখায়।
সপ্তাহ ডিফল্টভাবে রবিবার থেকে শুরু হয়, তবে week_start আর্গুমেন্ট দিয়ে এটি বদলানো যায় (সোমবারের জন্য 1 ব্যবহার করুন)।
dt <- make_datetime(2026, 4, 23)
# default behavior
wday(dt)
#> [1] 5
wday(dt, label = T)
#> [1] Thu
# set week_start to Monday
wday(dt, week_start = 1)
#> [1] 4
কয়েকটি ইউটিলিটি ফাংশন TRUE/FALSE মান রিটার্ন করে, যেমন leap_year() আর dst() (ডেলাইট সেভিং টাইম চালু আছে কি?)।
মাসের দৈর্ঘ্য আলাদা হয়, তাই days_in_month() ইন্টিজার মান দেয়।
date আর datetime দুটোই সময়রেখার একটি মুহূর্ত বোঝায়।
কীভাবে আমরা "গতকাল এই সময়" বা "এখন থেকে এক সপ্তাহ পর" ধরনের ধারণা সামলাই?
বিজ্ঞানভিত্তিক অন্য ভাষার সাথে পরিচিত শিক্ষার্থীরা Python-এর datetime.timedelta বা Julia-র Period টাইপ সম্পর্কে জানতে পারেন।
এর বিপরীতে, R-এ সময়ের ব্যাপ্তি উপস্থাপনের কমপক্ষে তিনটি উপায় আছে, যাদের শক্তি ও সীমাবদ্ধতা আলাদা। বরাবরের মতো, আপনার প্রসঙ্গের সাথে সবচেয়ে ভালো মানায় সেটিই বেছে নিন।
বিভিন্ন ব্যাপ্তি সহজেই একে অন্যের মধ্যে রূপান্তর করা যায়, as.period(), as_duration() আর as.interval() দিয়ে।
is.period() ইত্যাদি দিয়ে ব্যাপ্তির ধরন যাচাই করা যায়।
একটি period, যেমন hours(3), একটি ঘড়ির সময়কে ৩ ঘণ্টা বাড়াতে ব্যবহার করা যায়।
ডেলাইট সেভিং টাইম (DST)-এর মতো অনিয়মগুলো বেশিরভাগ ক্ষেত্রেই উপেক্ষা করা হয়।
সব পিরিয়ড হলো একটি এককের বহুবচন নাম: years, weeks, ইত্যাদি।
d <- today()
d
#> [1] "2026-04-25"
d + weeks(2)
#> [1] "2026-05-09"
# dates are coerced to datetimes at midnight if necessary
d - hours(20)
#> [1] "2026-04-24 04:00:00 UTC"
hours(3)
#> [1] "3H 0M 0S"
একটি duration, যেমন dhours(3), প্রকৃত সময়কে ৩ ঘণ্টা বাড়ায়, আর DST-এর মতো অনিয়ম মেনে চলে, যা ঘড়ির সময়কে প্রকৃত সময় থেকে বিচ্যুত করে।
সব ডিউরেশন সংশ্লিষ্ট পিরিয়ডের সামনে একটি d প্রিফিক্স যোগ করে: dyears, dweeks, ইত্যাদি।
dhours(3)
#> [1] "10800s (~3 hours)"
একটি interval হলো একটি শুরুর সময় ও শেষের সময়ের সমন্বয়, যা interval() ফাংশন বা %--% ইনফিক্স অপারেটর দিয়ে তৈরি হয়।
# the next 2 lines were run about 33s apart, so now() is different
dt1 <- now()
dt2 <- now() + hours(1)
# interval(0 function)
ivl <- interval(dt1, dt2)
ivl
#> [1] 2026-04-24 16:24:54 MST--2026-04-24 17:25:27 MST
# infix operator
iv2 <- dt1 %--% dt2
iv2 == iv1
#> [1] TRUE
টাইমজোন নিয়ে কাজ করা চ্যালেঞ্জিং হতে পারে। নিচের উদ্ধৃতিটি Python ডকুমেন্টেশন থেকে নেওয়া, তবে R-এর জন্যও সমান প্রাসঙ্গিক:
"সারা বিশ্বে সময় সমন্বয়ের নিয়মগুলো যুক্তির চেয়ে বেশি রাজনৈতিক, প্রায়ই বদলায়, আর UTC ছাড়া প্রতিটি অ্যাপ্লিকেশনের জন্য উপযুক্ত কোনো মানদণ্ড নেই।"
টাইমজোন নিয়ে কয়েকটি সাধারণ কথা:
datetime সবসময় একটি টাইমজোন (tz) ধারণ করে; একটি date টাইমজোন-সচেতন নয়।now() ফাংশনটি এর ব্যতিক্রম: এটি সিস্টেম ক্লক থেকে আপনার স্থানীয় সময় নেয়, tz সহ।
ডিফল্টভাবে স্থানীয় tz ব্যবহৃত হয়, তবে now("UTC") ইত্যাদি দিয়ে অন্যগুলোও নির্দিষ্ট করা যায়।# timezone defaults to UTC
local <- ymd_hm("2026-04-25 10:32")
local
#> [1] "2026-04-25 10:32:00 UTC"
# timezone can be specified
AZ <- ymd_hm("2026-04-25 10:32", tz = "America/Phoenix")
AZ
#> [1] "2026-04-25 10:32:00 MST"
# same clocktime, different tz
local - AZ
#> Time difference of -7 hours
ডিউরেশন দিয়ে টাইমজোন-সম্পর্কিত হিসাব পিরিয়ডের চেয়ে বেশি নির্ভরযোগ্য।
তারিখ ও ডেটটাইম নিয়ে কাজ করা সাধারণত Base R-এর চেয়ে lubridate দিয়ে অনেক সহজ।
এই কনসেপ্টটি একটি সংক্ষিপ্ত পরিচয় দেওয়ার চেষ্টা করেছে, তবে অনিবার্যভাবে অনেক বিস্তারিত বাদ পড়েছে।
পুনরাবৃত্তির ঝুঁকি নিয়েই বলি: ডকুমেন্টেশনই আপনার বন্ধু, তাই এটি ব্যবহার করুন।
এই অনুশীলনীতে আপনি নিউ ইয়র্কের একটি বিউটি সেলুনের জন্য অ্যাপয়েন্টমেন্ট শিডিউলার নিয়ে কাজ করবেন, যেটি ২০১২ সালের ১৫ সেপ্টেম্বর চালু হয়েছিল।
আপনার সামনে পাঁচটি কাজ আছে, যার সবকটিতেই অ্যাপয়েন্টমেন্টের তারিখ নিয়ে কাজ করতে হবে। সময় ২৪-ঘণ্টার ফরম্যাটে দেওয়া আছে (AM/PM নেই)। টাইমজোন সংক্রান্ত জটিলতা যতটা সম্ভব কম রাখতে ধরে নিন যে সবকিছু UTC ব্যবহার করে।
টেক্সটে লেখা একটি অ্যাপয়েন্টমেন্টের তারিখকে সংশ্লিষ্ট DateTime ফরম্যাটে পার্স করার জন্য schedule_appointment() ফাংশনটি ইমপ্লিমেন্ট করুন।
মনে রাখবেন, ইনপুটটি মার্কিন ধাঁচের মাস/দিন/বছর ক্রমে দেওয়া।
julia> schedule_appointment("07/25/2023 13:45:00")
2023-07-25T13:45:00
has_passed() ফাংশনটি ইমপ্লিমেন্ট করুন, যেটি একটি অ্যাপয়েন্টমেন্টের তারিখ নেয় এবং অ্যাপয়েন্টমেন্টটি অতীতে পড়ে কি না তা দেখে:
julia> has_passed(DateTime(1999, 12, 31, 9, 0, 0))
true
is_afternoon_appointment() ফাংশনটি ইমপ্লিমেন্ট করুন, যেটি একটি অ্যাপয়েন্টমেন্টের তারিখ নেয় এবং অ্যাপয়েন্টমেন্টটি বিকেলে (>= 12:00 এবং < 18:00) কি না তা দেখে:
julia> is_afternoon_appointment(DateTime(2023, 3, 29, 15, 0, 0))
true
day_of_week() ফাংশনটি ইমপ্লিমেন্ট করুন, যেটি সপ্তাহের সংখ্যাগত দিন রিটার্ন করে।
সেলুনটি রবিবার বন্ধ থাকে, তাই সোমবার হলো দিন ১ এবং শনিবার হলো দিন ৬।
# a Friday
day_of_week(make_datetime(2026, 06, 05, 12, 30, 00))
#> [1] 5
কোনো গ্রাহককে একটি অ্যাপয়েন্টমেন্টের নতুন সময় নির্ধারণ করতে হলে, এটি স্বয়ংক্রিয়ভাবে পরের শুক্রবারে একই সময়ে করে দেওয়া হয় (কারণ এই দিনে সপ্তাহের সবচেয়ে কম ভিড় থাকে)।
reschedule() ফাংশনটি ইমপ্লিমেন্ট করুন, যেটি মূল অ্যাপয়েন্টমেন্টটি (মাস/দিন/বছর ক্রমে লেখা একটি টেক্সট স্ট্রিং হিসেবে) নেয় এবং নতুন সময় নির্ধারিত একটি অ্যাপয়েন্টমেন্ট রিটার্ন করে।
# a Saturday
reschedule("06/13/2026 12:30:00")
#> [1] "2026-06-19 12:30:00 UTC"
যদি মূল অ্যাপয়েন্টমেন্টটি সোমবার থেকে বৃহস্পতিবারের মধ্যে হয়ে থাকে, তবে নতুন সময় নির্ধারণ করুন এই সপ্তাহের শুক্রবারে। শুক্রবার বা শনিবারের অ্যাপয়েন্টমেন্টের ক্ষেত্রে নতুন সময় নির্ধারণ করুন পরের সপ্তাহের শুক্রবারে।
Exercism-এ সাইন আপ করুন, R ট্র্যাকের 21টি কনসেপ্ট111টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।