diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c
index 07b26f05fb77f4cfd850058690c697ef8533741c..e68f375ef7874470303a0fcc9dcba146af4d46b9 100644
--- a/extmod/vfs_fat.c
+++ b/extmod/vfs_fat.c
@@ -35,6 +35,7 @@
 #include "lib/fatfs/diskio.h"
 #include "extmod/vfs_fat_file.h"
 #include "extmod/fsusermount.h"
+#include "timeutils.h"
 
 #define mp_obj_fat_vfs_t fs_user_mount_t
 
@@ -152,6 +153,94 @@ STATIC mp_obj_t fat_vfs_getcwd(mp_obj_t vfs_in) {
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_getcwd_obj, fat_vfs_getcwd);
 
+// Checks for path equality, ignoring trailing slashes:
+//   path_equal(/, /) -> true
+// second argument must be in canonical form (meaning no trailing slash, unless it's just /)
+STATIC bool path_equal(const char *path, const char *path_canonical) {
+    while (*path_canonical != '\0' && *path == *path_canonical) {
+        ++path;
+        ++path_canonical;
+    }
+    if (*path_canonical != '\0') {
+        return false;
+    }
+    while (*path == '/') {
+        ++path;
+    }
+    return *path == '\0';
+}
+
+/// \function stat(path)
+/// Get the status of a file or directory.
+STATIC mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) {
+    const char *path = mp_obj_str_get_str(path_in);
+
+    FILINFO fno;
+#if _USE_LFN
+    fno.lfname = NULL;
+    fno.lfsize = 0;
+#endif
+    FRESULT res;
+
+    if (path_equal(path, "/")) {
+        // stat root directory
+        fno.fsize = 0;
+        fno.fdate = 0;
+        fno.ftime = 0;
+        fno.fattrib = AM_DIR;
+    } else {
+        res = FR_NO_PATH;
+        for (size_t i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount)); ++i) {
+            fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[i];
+            if (vfs != NULL && path_equal(path, vfs->str)) {
+                // stat mounted device directory
+                fno.fsize = 0;
+                fno.fdate = 0;
+                fno.ftime = 0;
+                fno.fattrib = AM_DIR;
+                res = FR_OK;
+            }
+        }
+        if (res == FR_NO_PATH) {
+            // stat normal file
+            res = f_stat(path, &fno);
+        }
+        if (res != FR_OK) {
+            nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError,
+                MP_OBJ_NEW_SMALL_INT(fresult_to_errno_table[res])));
+        }
+    }
+
+    mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
+    mp_int_t mode = 0;
+    if (fno.fattrib & AM_DIR) {
+        mode |= 0x4000; // stat.S_IFDIR
+    } else {
+        mode |= 0x8000; // stat.S_IFREG
+    }
+    mp_int_t seconds = timeutils_seconds_since_2000(
+        1980 + ((fno.fdate >> 9) & 0x7f),
+        (fno.fdate >> 5) & 0x0f,
+        fno.fdate & 0x1f,
+        (fno.ftime >> 11) & 0x1f,
+        (fno.ftime >> 5) & 0x3f,
+        2 * (fno.ftime & 0x1f)
+    );
+    t->items[0] = MP_OBJ_NEW_SMALL_INT(mode); // st_mode
+    t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino
+    t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev
+    t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // st_nlink
+    t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid
+    t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid
+    t->items[6] = MP_OBJ_NEW_SMALL_INT(fno.fsize); // st_size
+    t->items[7] = MP_OBJ_NEW_SMALL_INT(seconds); // st_atime
+    t->items[8] = MP_OBJ_NEW_SMALL_INT(seconds); // st_mtime
+    t->items[9] = MP_OBJ_NEW_SMALL_INT(seconds); // st_ctime
+
+    return MP_OBJ_FROM_PTR(t);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_stat_obj, fat_vfs_stat);
+
 STATIC const mp_rom_map_elem_t fat_vfs_locals_dict_table[] = {
     { MP_ROM_QSTR(MP_QSTR_mkfs), MP_ROM_PTR(&fat_vfs_mkfs_obj) },
     { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&fat_vfs_open_obj) },
@@ -161,6 +250,7 @@ STATIC const mp_rom_map_elem_t fat_vfs_locals_dict_table[] = {
     { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&fat_vfs_getcwd_obj) },
     { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&fat_vfs_remove_obj) },
     { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&fat_vfs_rename_obj) },
+    { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&fat_vfs_stat_obj) },
 };
 STATIC MP_DEFINE_CONST_DICT(fat_vfs_locals_dict, fat_vfs_locals_dict_table);
 
diff --git a/unix/Makefile b/unix/Makefile
index a3c1197915379f94db2a1fdd634176a306912aad..90653e88e8982403c3a6196c5ec82f74df387f6f 100644
--- a/unix/Makefile
+++ b/unix/Makefile
@@ -15,6 +15,7 @@ include ../py/py.mk
 
 INC +=  -I.
 INC +=  -I..
+INC += -I../lib/timeutils
 INC += -I$(BUILD)
 
 # compiler settings
@@ -163,6 +164,7 @@ LIB_SRC_C = $(addprefix lib/,\
 	utils/printf.c \
 	fatfs/ff.c \
 	fatfs/option/ccsbcs.c \
+	timeutils/timeutils.c \
 	)
 
 OBJ = $(PY_O)