Lesson Review
Built-in Modules: datetime, os & sys
datetime.now()strftime("%A")weekday()os.path.exists()os.listdir()sys.argv
Python comes with tools for dates and times, files and folders, and the command line. You only have to import them.
By the end of this lesson you will be able to:
- Use the datetime module to get the current date and time with
now(), format it with strftime(), and find the day of the week with weekday().
- Use the os and os.path modules to check whether a file exists and to list, create, rename, and delete files and folders.
- Use the sys module to read command-line arguments passed to a Python script.
How to use this page: Use Next and Back (or the arrow keys). The file examples run on a pretend drive inside this page, so nothing on your computer is ever read, changed, or deleted. Every Run starts with a fresh copy of the pretend drive.
Words you will hear today
| Word | What it means | Example |
| module | A file of ready-made code you can import | datetime, os, sys |
| standard library | The modules that come with every Python install | no download needed |
| object / attribute | A value with named parts you reach with a dot | now.year |
| format code | A % code that says how to show part of a date | %B for the month name |
| path | The text that says where a file or folder is | "data/day1.txt" |
| command-line argument | A word typed after the script name when you run it | python greet.py Ava |
- datetime: now, strftime, weekday.
- os and os.path: exists and file operations.
- sys: argv.
- Module, standard library, attribute, format code, path, argument.
So far every program we wrote lived in its own little world. Today we reach outside of it. The datetime module reads the clock, the os module talks to the files and folders on the computer, and the sys module reads the extra words you type when you start a program. All three come with Python, so there is nothing to install.
Modules
The standard library and import
The tutorial: "Python comes with a library of standard modules." A module is a file of ready-made code. You load one with import at the top of your program.
| You write | Then you use |
import datetime | datetime.date(2026, 10, 9) |
import datetime as dt | dt.date(2026, 10, 9) |
from datetime import date | date(2026, 10, 9) |
import os | os.getcwd(), os.path.exists("a.txt") |
import sys | sys.argv |
Name trap: the datetime module contains a class that is also named datetime. After import datetime, write datetime.datetime.now(). Or use from datetime import datetime, then datetime.now().
Which import style should I use?
import os: you always write os.something, so it is clear where every tool comes from. Good for os and sys.
from datetime import datetime, date: shorter to use, and avoids the datetime.datetime confusion. Good for this module.
import datetime as dt: a short nickname. Use it when a name is long and you use it a lot.
Don't name your file after a module. If you save your program as datetime.py or random.py, Python finds your file first when you import, and the real module's tools seem to be missing. Pick a different file name.
- import module, import module as name, from module import name.
- datetime module vs datetime class.
- Never name your file datetime.py, os.py, or random.py.
A module is a toolbox someone already built for you. The import line opens the toolbox. With plain import, you say the module name every time, with a dot. With from import, you pull one tool out of the box so you can use it by itself. Try the trap option: it is the most common datetime error there is.
datetime
Reading the clock: now() and today()
datetime.now(): the docs say "Return the current local date and time."
date.today(): "Return the current local date."
- The answer comes from the computer's clock, so it is different every time you run.
from datetime import datetime, date
now = datetime.now()
print(now) # 2026-10-09 14:05:03.123456
print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)
today = date.today()
print(today) # 2026-10-09
| Object | Attributes (all int) |
date | year month day |
datetime | those, plus hour minute second microsecond |
Run it a few times and watch the time change.
Note: a web browser only reports thousandths of a second, so on this page the last three digits of the microseconds are always 000. Real Python shows all six.
Using the attributes
Because now.hour and the others are ints, you can make decisions with them:
from datetime import datetime
now = datetime.now()
if now.hour < 12:
print("Good morning!")
elif now.hour < 18:
print("Good afternoon!")
else:
print("Good evening!")
No parentheses on attributes: now.year is a stored value, not a function, so now.year() is a TypeError. But now(), today(), and weekday() are functions and need them.
Dates compare like numbers: date(2026, 10, 9) < date(2026, 12, 25) is True, because October 9 comes first.
- now() = date and time. today() on date = date only.
- Attributes are ints, so you can do math with them.
- Attributes like .year are ints with no parentheses.
- Dates can be compared with < and >.
The now function asks the computer what time it is right now. The result is an object, and you read its parts with a dot: year, month, day, hour, minute, second. Those parts are plain integers, so you can compare them or do math with them. Notice that hour uses a 24-hour clock: 2 PM is 14.
datetime
Formatting dates with strftime()
strftime(): "Return a string representing the date and time, controlled by an explicit format string." Each format code starts with %. Everything else is copied as is.
| Code | Means | For Oct 9, 2026 at 2:05:03 PM |
%Y / %y | year (4 or 2 digits) | 2026 / 26 |
%m | month number | 10 |
%B / %b | month name | October / Oct |
%d | day of the month | 09 |
%A / %a | weekday name | Friday / Fri |
%H | hour, 24-hour clock | 14 |
%I and %p | hour, 12-hour clock, AM/PM | 02 PM |
%M | minute (not month!) | 05 |
%S | second | 03 |
%j | day of the year | 282 |
%% | a real % sign | % |
The same codes work in an f-string after a colon: f"{now:%B %d}".
Type a format string, or click the codes to add them.
Build a format one piece at a time
To show Friday, October 09, 2026, list each part and its code, then copy the punctuation between them exactly:
Friday , October 09 , 2026
%A , %B %d , %Y → "%A, %B %d, %Y"
Anything that is not a % code (commas, spaces, slashes, words like "at") appears in the result exactly as typed.
Going the other way: strptime()
strftime turns a date into text (f for format). strptime reads text into a date (p for parse), using the same codes:
d = datetime.strptime("10/09/2026", "%m/%d/%Y")
print(d) # 2026-10-09 00:00:00
- Lowercase m = month, uppercase M = minute.
- %I + %p for a 12-hour clock.
- strftime: date to text. strptime: text to date.
Printing a datetime gives you the computer's format, with dashes and a 24-hour clock. People usually want something friendlier, like Friday, October 9. That is what strftime is for. You write a pattern, and each percent code gets replaced with part of the date. Be careful with capital letters: little m is the month, big M is the minute.
datetime
What day of the week? weekday()
weekday(): "Return the day of the week as an integer, where Monday is 0 and Sunday is 6."
isoweekday(): the same, but "Monday is 1 and Sunday is 7."
- Make your own date with
date(year, month, day). Python checks it for you.
| Day | Mon | Tue | Wed | Thu | Fri | Sat | Sun |
weekday() | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
isoweekday() | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
d = date(2026, 10, 9)
print(days[d.weekday()]) # Fri
print(d.strftime("%A")) # Friday
A weekday number makes a perfect list index. And d.weekday() >= 5 is a quick weekend check.
Try your birthday, a holiday, or an impossible date like month 13 or February 30.
Why Monday is 0
Python follows the international standard (ISO 8601) where the week starts on Monday. If you are used to calendars that start on Sunday, write a list that starts with Monday and you can never be off by one.
Using the number
if d.weekday() >= 5: # 5 = Saturday, 6 = Sunday
print("Weekend!")
else:
print("School day.")
Python checks real calendars: date(2024, 2, 29) works because 2024 is a leap year, but date(2026, 2, 29) is a ValueError because 2026 is not.
- Monday = 0, Sunday = 6. Not Sunday = 0!
- Bad dates raise ValueError.
- weekday() >= 5 means Saturday or Sunday.
- Python knows leap years.
Many calendars start the week on Sunday, but Python's weekday starts on Monday at zero. Since the answer is a number from 0 to 6, it works as an index into a list of day names. Try month 13 or February 30: Python refuses to build a date that does not exist.
datetime
Date math with timedelta
The docs: "A timedelta object represents a duration, the difference between two datetime or date instances."
| Expression | Gives |
date - date | a timedelta (how long between) |
date + timedelta(days=14) | a new date |
datetime - datetime | a timedelta with hours and minutes |
delta.days | whole days as an int |
delta.total_seconds() | the length in seconds (float) |
from datetime import date, timedelta
today = date(2026, 10, 9)
trip = date(2026, 12, 21)
print(trip - today) # 73 days, 0:00:00
print((trip - today).days) # 73
print(today + timedelta(days=14)) # 2026-10-23
timedelta accepts weeks, days, hours, minutes, and seconds.
A countdown program
from datetime import date
today = date.today()
break_starts = date(2026, 12, 21)
days_left = (break_starts - today).days
print(f"{days_left} days until winter break!")
Subtracting two dates gives a timedelta; .days turns it into a plain int you can print or compare.
Working out someone's age
born = date(2010, 5, 17)
today = date(2026, 10, 9)
age = today.year - born.year
if (today.month, today.day) < (born.month, born.day):
age -= 1 # birthday hasn't happened yet this year
print(age) # 16
The tuple comparison checks the month first, then the day, just like the tuples lesson.
- Subtract dates to get a timedelta; add a timedelta to get a date.
- You cannot add a plain number to a date.
- (later_date - earlier_date).days gives days between.
How many days until break? When is this assignment due if you get two weeks? These are date math questions. Subtracting two dates gives a timedelta, which is a length of time. Adding a timedelta to a date moves the date. Adding a plain number does not work, because Python cannot know if you mean days, hours, or years.
Practice
Format challenge
Type the format string (the part inside the quotes) that turns d into each target, then click Check. Practice only; nothing is recorded.
Codes people mix up most
| Lowercase | Uppercase |
%m month number (10) | %M minute (05) |
%y 2-digit year (26) | %Y 4-digit year (2026) |
%a short day (Fri) | %A full day (Friday) |
%b short month (Oct) | %B full month (October) |
A pattern to remember: for names, lowercase is short and uppercase is full.
- Check the capital letters: %m vs %M, %I vs %H.
Look at each target and break it into pieces: which parts come from the date, and which are just punctuation you type yourself? Commas, slashes, colons, and spaces are copied exactly as you write them.
os
The os module: where am I?
os.getcwd(): "Return a string representing the current working directory." That is the folder your program is running in.
os.listdir(path): "Return a list containing the names of the entries in the directory given by path." With no path, it lists the current folder.
- The docs also say "The list is in arbitrary order", so use
sorted() when the order matters.
A relative path like "data/day1.txt" starts from the current folder. An absolute path starts from the top of the drive, like "/home/student/project/data/day1.txt".
On Windows, paths use backslashes and a drive letter, like C:\Users\ava\project. On Mac and Linux they use forward slashes.
Why the current folder matters
When your program opens "notes.txt" with no folder in front, Python looks in the current working directory. If you run the program from a different folder, the same line may not find the file. Printing os.getcwd() is the first thing to try when a file "isn't there" but you can see it.
Listing only some files
for name in sorted(os.listdir()):
if name.endswith(".txt"):
print(name)
listdir gives plain strings, so all your string tools (endswith, startswith, in) work on the names.
- getcwd = current folder. listdir = names inside a folder.
- listdir order is arbitrary: sort it.
- File not found? Print os.getcwd() first.
- Filter listdir() names with endswith().
Every running program has a current working directory, the folder it is "standing in." File names without a full path are looked up there. The pretend drive on this slide has a project folder with three files and a data folder. Use listdir to look inside, and notice that listdir gives back plain strings, not the files themselves.
os.path
Checking files: exists, isfile, isdir
| Function | From the docs |
os.path.exists(path) | "Return True if path refers to an existing path" |
os.path.isfile(path) | "Return True if path is an existing regular file." |
os.path.isdir(path) | "Return True if path is an existing directory." |
os.path.getsize(path) | "Return the size, in bytes, of path. Raise OSError if the file does not exist" |
import os
if os.path.exists("notes.txt"):
print("Found it!")
else:
print("No notes yet.")
exists, isfile, and isdir never crash on a missing file; they just return False. This pretend drive is case-sensitive like Linux, so "Notes.txt" is not found. On Windows and on most Macs, file names are not case-sensitive.
Type a name to check. Try data, data/day1.txt, Notes.txt, and report.txt.
exists vs. isfile vs. isdir
| Name | exists | isfile | isdir |
a file, like notes.txt | True | True | False |
a folder, like data | True | False | True |
| nothing by that name | False | False | False |
Use the most specific check for the job. If you are about to read a file, isfile is better than exists, because exists would also be True for a folder with that name.
- exists: file or folder. isfile: files only. isdir: folders only.
- getsize raises an error for a missing file.
- Use isfile before reading a file, not just exists.
Before you open, delete, or create something, you usually want to know if it is already there. These three functions answer with True or False, and they are safe to call even when the file is missing. getsize is different: it needs the file to exist, or it raises an error.
os.path
Building and splitting paths
| Call | Result (Mac and Linux) |
os.path.join("data", "day1.txt") | 'data/day1.txt' |
os.path.basename("data/day1.txt") | 'day1.txt' |
os.path.dirname("data/day1.txt") | 'data' |
os.path.splitext("photo.png") | ('photo', '.png') |
join is the safe way to glue folder and file names together. The docs: "Join one or more path segments intelligently." It uses the right separator for the computer, so on Windows the first row gives 'data\\day1.txt' (one backslash, shown doubled in the repr).
splitext returns a tuple, so you can unpack it: name, ext = os.path.splitext("photo.png").
Why not just use +?
"data" + "/" + "day1.txt" works on Mac and Linux but uses the wrong separator on Windows, and it is easy to end up with doubled or missing slashes. os.path.join always gets the separator right for whatever computer runs the program.
These functions only work with text
basename, dirname, join, and splitext never look at the drive. os.path.join("nope", "fake.txt") happily gives 'nope/fake.txt' even though neither exists. Use exists if you need to know whether it is real.
splitext only splits off the last extension: os.path.splitext("a.tar.gz") gives ('a.tar', '.gz').
- join builds, basename/dirname/splitext take apart.
- These only work with the text of the path; they do not check the drive.
- os.path.join uses the right separator for any computer.
Paths are just strings, but gluing them together with plus signs is risky, because Windows and Mac use different slashes. Let os.path.join do it. The other three take a path apart: the file's name, the folder it is in, and the extension. These functions only look at the text; they never check whether the file is really there.
os
Creating, renaming, and deleting
| Call | Does | Error if |
os.mkdir("reports") | makes one folder | it already exists, or the parent folder is missing |
os.makedirs("a/b/c") | makes a folder and any missing parents | it already exists (unless exist_ok=True) |
os.rename("old.txt", "new.txt") | renames or moves | the old name is missing |
os.remove("old.txt") | deletes a file | it is missing or is a folder |
os.rmdir("reports") | deletes an empty folder | it is missing or not empty |
Careful: os.remove deletes for real. The file does not go to the Recycle Bin or Trash. Also, on Mac and Linux, renaming onto a file that already exists silently replaces it; on Windows the docs say "a FileExistsError is always raised."
Pick a line 2, run it, and watch the pretend drive change.
Think before you change files
Programs that create, rename, or delete files can do a lot of damage quickly, especially inside a loop. Good habits:
- Test first with print: print what the program would delete or rename, check the list, and only then switch to the real
os.remove or os.rename.
- Work in a test folder with copies of your files.
- Use
exist_ok=True with makedirs when it is fine if the folder is already there.
Why two delete functions? Deleting a folder is riskier than deleting one file, so os.rmdir only removes empty folders. You have to remove the files inside on purpose first.
- mkdir/makedirs create, rename moves, remove deletes files, rmdir deletes empty folders.
- Print what you would delete before deleting for real.
These are the same things you do in File Explorer or Finder, but from code. That is powerful: a program can organize hundreds of files in a second. It is also dangerous: remove does not ask "are you sure?" and does not use the trash. Try the options that cause errors and read the error names; they tell you exactly what went wrong.
os
Two safe patterns: check first, or try
Look before you leap: check with os.path, then act.
if os.path.exists("report.txt"):
os.remove("report.txt")
else:
print("Nothing to delete")
Try it and handle the error:
try:
os.remove("report.txt")
except FileNotFoundError:
print("Nothing to delete")
| Error | Typical cause |
FileNotFoundError | the name is not there |
FileExistsError | making something that already exists |
IsADirectoryError / NotADirectoryError | a folder where a file was expected, or the other way around |
OSError | the parent of all of these, so except OSError catches any of them |
Which pattern should I use?
Both work for simple programs. The try version is a little safer: between your exists check and the remove, another program could delete or create the file. With try, you simply attempt the action and handle whatever actually happens.
Catch the specific error. except FileNotFoundError: handles the case you expected. A bare except: would also hide unrelated bugs, like a typo in a variable name.
- Check first with exists, or try/except the specific error.
- All file errors are kinds of OSError.
- try/except handles what actually happens; catch the specific error.
File code can fail for reasons outside your program: someone deleted the file, or the folder already exists. Good programs expect that. You can check first with exists, or you can just try and catch the error. Both are fine. Try each pattern with a file that exists and with one that does not.
sys
Command-line arguments: sys.argv
You can start a script from a terminal and type extra words after its name. Those words are command-line arguments.
sys.argv: "The list of command line arguments passed to a Python script. argv[0] is the script name".
- Every item is a string, even
'16'.
- Spaces separate arguments. Quotes keep words together:
"Ava Lee" is one argument.
len(sys.argv) is always at least 1, because the script name counts.
To run a script this way, open a terminal in the script's folder (most editors have one built in) and type the command. On Windows you can also type py instead of python.
Type a command and press Enter, or click an example.
Reading python greet.py Ava 16
| Index | Value | Type |
sys.argv[0] | 'greet.py' | str |
sys.argv[1] | 'Ava' | str |
sys.argv[2] | '16' | str (not a number yet!) |
The word python itself is not in the list; it is the program that runs your script.
Why use arguments instead of input()? The program can run without anyone typing answers, so it can be started by another program, a schedule, or a shortcut, with the information given up front.
- argv[0] = script name; argv[1] = first argument.
- All strings: convert with int() for math.
- python is not in argv; the script name is argv[0].
When you click Run in an editor, the editor types a command for you behind the scenes. In a terminal, you can type that command yourself and add extra words at the end. Python collects every word into a list called sys.argv. Slot zero is the script's own name, and your words start at slot one. Try the show_args script with your own words.
sys
Using arguments safely and sys.exit()
import sys
if len(sys.argv) < 3:
sys.exit("Usage: python age.py NAME AGE")
name = sys.argv[1]
age = int(sys.argv[2]) # '16' becomes 16
print(f"Hi {name}! Next year you will be {age + 1}.")
- Check
len(sys.argv) first. A missing argument means sys.argv[2] raises IndexError: list index out of range.
- Convert with
int() or float() before doing math.
sys.exit() stops the program. The docs: sys.exit("some error message") "is a quick way to exit a program when an error occurs." The message is printed and the exit code is 1.
- An exit code of 0 means success; "any nonzero value is considered 'abnormal termination'".
Change the arguments and run. Try leaving them blank, Ava sixteen, and "Ava Lee" 16.
Why check the length first?
You cannot control what people type. Checking len(sys.argv) before using sys.argv[2] means a missing argument gives a helpful "Usage" message instead of an IndexError.
A bad number still crashes: python age.py Ava sixteen passes the length check, but int("sixteen") is a ValueError. Wrap the conversion in try / except ValueError to handle it:
try:
age = int(sys.argv[2])
except ValueError:
sys.exit("AGE must be a whole number")
Other handy sys tools: sys.version shows which Python is running, and sys.platform tells you the operating system (like 'win32' or 'linux').
- Check len(sys.argv) before indexing.
- sys.exit("message") prints the message and exits with code 1.
- Check len(sys.argv), then try/except ValueError around int().
A program that reads arguments must expect people to forget them or type them wrong. The usual pattern is to check the length of sys.argv first and, if something is missing, call sys.exit with a usage message that shows the right way to run the program. Watch the exit code at the bottom of the terminal change.
Debugging
Common mistakes
| Mistake | Code | What happens (Python 3.13 on Linux) | Fix |
| Module vs class name | import datetime
datetime.now() | AttributeError: module 'datetime' has no attribute 'now' | from datetime import datetime |
| Forgot the import | os.getcwd() | NameError: name 'os' is not defined. Did you forget to import 'os'? | import os |
| %M for the month | d.strftime("%M/%d") | No error. %M is the minute | "%m/%d" |
| Thinking Sunday is 0 | d.weekday() | No error. Monday is 0, Sunday is 6 | Use a list that starts with Monday |
| A date that does not exist | date(2026, 13, 1) | ValueError: month must be in 1..12 | Check the month and day |
| Adding a number to a date | today + 7 | TypeError: unsupported operand type(s) for +: 'datetime.date' and 'int' | today + timedelta(days=7) |
| Deleting a missing file | os.remove("nope.txt") | FileNotFoundError: [Errno 2] No such file or directory: 'nope.txt' | Check os.path.exists first |
| Making a folder twice | os.mkdir("data") | FileExistsError: [Errno 17] File exists: 'data' | os.makedirs("data", exist_ok=True) |
| Deleting a folder with remove | os.remove("data") | IsADirectoryError: [Errno 21] Is a directory: 'data' | os.rmdir (empty folders only) |
| Missing argument | sys.argv[1] with no arguments | IndexError: list index out of range | Check len(sys.argv) first |
| Math on an argument | sys.argv[1] + 1 | TypeError: can only concatenate str (not "int") to str | int(sys.argv[1]) + 1 |
Windows words some file errors differently. For example, a missing file shows [WinError 2] The system cannot find the file specified.
Try these at the prompt (it uses the same pretend drive):
import os
sorted(os.listdir())
os.path.exists("notes.txt")
os.remove("nope.txt")
from datetime import datetime
d = datetime(2026, 10, 9, 14, 5)
d.strftime("%A %I:%M %p")
d.weekday()
Sort the error by module
- datetime: AttributeError usually means module vs. class (
datetime.datetime). A ValueError means an impossible date. A TypeError means you added a plain number instead of a timedelta.
- os: errors with
[Errno ...] are file system errors. Read the error type (NotFound, Exists, IsADirectory) and the file name at the end.
- sys: IndexError means too few arguments; TypeError or ValueError means you forgot to convert a string.
- No error, wrong output: check format codes (
%m vs. %M) and the weekday numbering.
- Import errors, format code mix-ups, file errors, and argv errors.
- [Errno ...] errors come from the file system; read the type and file name.
Two of these give no error at all: using capital M for the month, and thinking Sunday is day zero. Those are the sneaky ones, because your program runs and just prints the wrong thing. For the file errors, read the error name: it tells you if the file was missing, already there, or a folder.
Practice
Check yourself
The file questions use this page's pretend drive: notes.txt, scores.csv, photo.png, and a folder named data. Choose an answer, then click Check. Practice only; nothing is graded or recorded.
Hints:
- For format questions, match each part of the output to a code, and check capital letters.
- For weekday questions, count from Monday = 0.
- For file questions, check the exact name and case against the pretend drive.
- For argv questions, remember argv[0] is the script and every item is a string.
- October 9, 2026 is a Friday.
Take your time with the format questions and say each code out loud: little m month, big M minute, big I twelve-hour clock. For the sys.argv questions, remember that the script name is always in slot zero.
Summary
Cheat sheet
datetime
from datetime import datetime, date, timedelta
now = datetime.now()
today = date.today()
now.year now.hour
now.strftime("%A, %B %d, %Y")
now.strftime("%I:%M %p")
today.weekday() # Mon = 0
today + timedelta(days=7)
os and os.path
import os
os.getcwd()
sorted(os.listdir())
os.path.exists(p) isfile isdir
os.path.getsize(p)
os.path.join("data", "a.txt")
os.mkdir(d) os.makedirs(d)
os.rename(old, new)
os.remove(f) os.rmdir(d)
sys
import sys
# python greet.py Ava 16
sys.argv # ['greet.py', 'Ava', '16']
sys.argv[0] # script name
len(sys.argv)
int(sys.argv[2])
sys.exit("Usage: ...")
Remember:
import datetime gives the module; the class is datetime.datetime
%m is month, %M is minute; weekday() starts at Monday = 0
- Check with
os.path.exists or use try/except FileNotFoundError
os.remove deletes for real, and only files; os.rmdir only removes empty folders
- Every item in
sys.argv is a string, and argv[0] is the script name
Key words to know
module · standard library · import / from ... import / as · attribute · strftime / strptime · timedelta · current working directory · relative / absolute path · sys.argv · exit code
- datetime, os, sys.
- Know the key words: strftime, timedelta, working directory, sys.argv.
Let's wrap up. The datetime module reads the clock and formats dates with strftime. The os module looks at and changes files and folders, so always check first or catch the error. And sys.argv hands your program the words typed after its name. These three modules show up in almost every real program you will write.