Perl, Ruby and Other Languages
In brief: Perl and Ruby both fixed their built-in time handling for 2038 around 2010, so current versions work past 2038 even on 32-bit machines. Erlang/Elixir are safe because their integers grow as needed. COBOL and Fortran programs usually don't store Unix time at all, but they can still receive it from other systems. Shell scripts,
awk, Lua and SQL are only as safe as the system or database underneath them.
| Risk level | Low for modern Perl/Ruby/Erlang. Medium for shell, awk and SQL on 32-bit or legacy systems. Varies for COBOL/Fortran (interfaces) |
| Status | Perl ≥ 5.12 and Ruby ≥ 1.9.2 have no internal 2038 limit. Others depend on platform time_t or column types |
| Last reviewed | 2026-10-21 |
2038 status at a glance
- At risk ConfirmedBinary formats written with 32-bit pack codes (Perl/Ruby pack 'l', 'N')These write 32-bit fields regardless of the language's internal range.Do: Use 64-bit pack codes and version the file format.
- At risk ConfirmedGNU Fortran TIME(), Lua with LUA_32BITS, 32-bit date/gawk buildsEach returns or stores a 32-bit time value.Do: Use TIME8(), default 64-bit Lua integers, or time64 builds.
- DependsCOBOL systems that receive Unix timeCOBOL rarely uses Unix time internally, but PIC S9(9) COMP fields fed by other systems are 32-bit.Do: Check interface fields that carry epoch seconds.
Not affected or different deadline (1), and why
- Not affectedPerl 5.12+, Ruby 1.9.2+, Erlang/ElixirPerl 5.12 and Ruby 1.9.2 (both 2010) moved to 64-bit or arbitrary-precision internal time, even on 32-bit machines. Erlang integers grow as needed.Do: Still check pack('l'), XS modules and fixed-width binary matches.
Perl
What changed
Perl's time, localtime and gmtime used the platform's C functions, so on 32-bit systems they stopped at 2038. Michael Schwern's y2038 project, funded in part by a Perl Foundation grant in 2008 [1][2], wrote a "drop-in replacement for POSIX time.h" with 64-bit localtime()/gmtime() that works on 32-bit machines while still using the system time zone database [1]. It landed in Perl 5.12.0, whose perldelta says: "Perl's core time-related functions are now Y2038 compliant. (It may not mean much to you, but your kids will love it!)" [3]. The y2038 wiki confirms the work has been "in Perl since 5.12.0" [1]. The same C library also went to CPAN as Time::y2038 for older Perls [4].
Time::Local
Time::Local::timelocal()/timegm() convert broken-down times to epoch seconds. Its docs warn that on older Perls the range depends on time_t: "Currently, this is 32 bits for most systems, yielding an approximate range from Dec 1901 to Jan 2038" [5]. Red Hat bug 1057047 shows the real-world effect. On RHEL 5's Perl 5.8.8, timegm(0,0,0,1,1,2038) died with "Cannot handle date". Time::Local 1.19 (Perl 5.8.9) fixed 64-bit systems, but "32-bit platforms will still refuse such requests". The 5.12 changes were judged too invasive to backport [6].
Time::Local has a separate pitfall: years 0–99 are read in a rolling century ("50 years on either side of the current year"). Use timegm_modern()/timelocal_modern(), which "simply take the year value as provided" [5].
use Time::Local qw(timegm timegm_modern);
my $t = timegm(0, 0, 0, 1, 0, 38); # BUG: '38' is a 2-digit year (rolling century)
my $u = timegm_modern(0, 0, 0, 1, 0, 2100); # FIX: explicit 4-digit year -> 4102444800
print scalar gmtime(2**31), "\n"; # Tue Jan 19 03:14:08 2038 (Perl 5.40, 64-bit)
Remaining Perl risks: pack('l', $t) / pack('N', $t) in binary protocols (32-bit signed/unsigned), XS modules that use C time_t, and Perls older than 5.12 still running on 32-bit hosts (RHEL 5/6-era appliances). Time::y2038 notes that Perl's floating-point numbers lose precision beyond about 2⁵² [4].
Ruby
Before Ruby 1.9.2, a Time object wrapped the C time_t. On 32-bit systems it simply wrapped around in 2038. Ruby 1.9.2 (August 2010) reimplemented Time so that "there is no longer the year 2038 problem". The internal value became a Ruby Integer that can grow into a Bignum [7]. Today's documentation says Time stores "a signed 63 bit integer, Integer (Bignum) object or Rational object", so "there are no boundary or resolution limitations". The fast 63-bit path covers years 1823–2116 [8].
Ruby still calls the C localtime()/gmtime() for time-zone conversion. With a 32-bit time_t (1901-12-14 to 2038-01-19), Ruby extrapolates local time beyond that range, so DST rules for far-future dates may be approximate [8]. A 2012 report showed Time.mktime(2038, 1, 19, 11, 14, 8) failing on Ruby 1.9.3 for 32-bit MinGW, so check old Windows builds [7].
Time.at(2**31).utc # => 2038-01-19 03:14:08 UTC
[Time.now.to_i].pack('l') # BUG: 32-bit signed field
[Time.now.to_i].pack('q') # FIX: 64-bit
Erlang and Elixir
Erlang integers are arbitrary-precision. A "small integer" fits in one word (60 bits on 64-bit, 28 bits on 32-bit), and larger values transparently become "large integers" (bignums) [9]. erlang:system_time/0,1 and os:system_time/0,1 (OTP 18+) return these integers, by default in native time units, so they have no 2038 overflow [10]. Elixir's System.os_time/1 and DateTime sit on the same machinery. The risk is binary pattern matching with fixed widths:
<<Ts:32/signed, Rest/binary>> = Packet, %% BUG: 32-bit field
<<Ts:64/signed, Rest/binary>> = Packet. %% FIX
COBOL
COBOL programs usually store dates as display or packed-decimal YYYYMMDD fields or as Lilian/integer day counts, not as Unix seconds. A February 2026 mainframe review sums it up: "By themselves, COBOL and PL/I are unlikely to be an issue: they don't internally store dates as UNIX datetimes… COBOL and PL/I functions must get a UNIX datetime stamp from somewhere else" [11]. The "somewhere else" includes z/OS UNIX services, Db2 or MQ fields, and files from distributed systems. The same review says z/OS has handled post-2038 dates since z/OS 2.3 (2017), and that from z/OS 2.4 (2019) "all application APIs should support 2038" [11]. Audit copybooks for PIC S9(9) COMP (a 4-byte binary field) that holds epoch seconds. The Y2K-era 2-digit-year windowing in COBOL is a separate issue; see Y2K-Lessons-for-2038.
Fortran
GNU Fortran's non-standard TIME() intrinsic "returns the current time encoded as an integer (in the manner of the function time(3))" as a INTEGER(4). The manual warns it is "not fully portable" and that values "might be, or become, negative". TIME8() is the 64-bit alternative [12]. Standard DATE_AND_TIME is safe.
integer(4) :: t4
integer(8) :: t8
t4 = time() ! BUG: INTEGER(4) — goes negative after 2038-01-19
t8 = time8() ! FIX (GNU extension); or use DATE_AND_TIME
SQL dialects (brief)
Engines and storage types are covered in Databases. Language-level traps:
- MySQL / MariaDB:
TIMESTAMPends at 2038-01-19 03:14:07 UTC.UNIX_TIMESTAMP()andFROM_UNIXTIME()were limited to 2038 before MySQL 8.0.28, and still are on 32-bit platforms. On 64-bit 8.0.28+ the limit is year 3001. Out-of-range input returns 0, not an error [13]. - SQL Server:
DATEADD(second, @epoch, '1970-01-01')takes an int number on SQL Server 2017–2022, so it fails after 2038 withMsg 8115 Arithmetic overflow. SQL Server 2025 and Azure SQL accept bigint [14]. - Any dialect:
INTcolumns used as epoch seconds. UseBIGINTor native date-time types.
Shell date, awk and Lua
date(GNU coreutils) uses the systemtime_t. On 64-bit Linux,date -u -d @2147483648printsTue 19 Jan 2038 03:14:08 UTC(coreutils 9.7, tested 2026-10-21). On a 32-bit build without 64-bit time it reports an invalid date or wraps. Coreutils is built on Gnulib, whoseyear2038module turns on_TIME_BITS=64automatically on 32-bit glibc ≥ 2.34 [15]. (We didn't confirm which coreutils release first used that module.) BusyBoxdateon embedded systems depends on its libc (musl ≥ 1.2 is fine). Test anydate +%sarithmetic in shell scripts on the real target.- awk: gawk's
systime(),mktime()andstrftime()use the Ctime_t[16]. gawk 5.2 on 64-bit Linux formats2147483648correctly (tested 2026-10-21). Awk numbers are doubles, so arithmetic is fine. The limit is the C library on 32-bit builds. mawk and BusyBox awk differ in what time functions they support. - Lua:
os.time()returns "a number, whose meaning depends on your system". On POSIX and Windows it is seconds since an epoch [17]. Standard Lua 5.3+ uses 64-bit integers, but it "can also compile Lua so that it uses 32-bit integers" viaLUA_32BITS, which is common on microcontrollers (NodeMCU, embedded builds). There,os.time()values overflow in 2038 [17].
Affected versions / products
| Language / tool | Safe from | 2038 exposure |
|---|---|---|
| Perl | 5.12.0 (internal 64-bit time) | Older Perls on 32-bit; pack('l'); XS modules |
| Ruby | 1.9.2 (Bignum/Rational Time) |
pack('l'); extrapolated local time on 32-bit time_t |
| Erlang/Elixir | all OTP 18+ time APIs | Fixed-width binary matches |
| COBOL | n/a (no Unix time internally) | Unix-time fields received from other systems |
| GNU Fortran | TIME8(), DATE_AND_TIME |
TIME() returns INTEGER(4) |
| MySQL SQL | 8.0.28+ on 64-bit for functions | TIMESTAMP type always; 32-bit builds |
| SQL Server T-SQL | 2025 / Azure (DATEADD bigint) |
2017–2022 DATEADD(second, epoch, …) |
| GNU date / gawk | 64-bit time_t builds |
32-bit builds without _TIME_BITS=64 |
| Lua | default 64-bit integers | LUA_32BITS builds |
Risk & compliance notes
- Shell, awk and Perl "glue" scripts are rarely inventoried, but they often compute expiry dates (
date -d "+20 years"), and those already cross 2038. - Mainframe exposure is at the interfaces: map epoch-second feeds into COBOL copybooks. Owners: operations, mainframe teams, DBAs.
Detection & mitigation
- Quick probes on each target host:
date -u -d @2147483648,perl -e 'print scalar gmtime(2**31)',ruby -e 'p Time.at(2**31).utc',awk 'BEGIN{print strftime("%Y",2147483648,1)}'. - Grep for
pack(['"][lLN](Perl/Ruby),:32binary matches (Erlang),PIC S9(9) COMP(COBOL),time()(Fortran),INTepoch columns andDATEADD(s(SQL). - Upgrade Perl/Ruby on 32-bit legacy hosts. Rebuild Lua without
LUA_32BITSwhere memory allows. - See Testing-and-Detection and Remediation-Guide.
See also
- Programming-Languages
- Lang-C-and-CPP, Lang-Python
- Databases
- OS-Linux, OS-Embedded-and-RTOS
- Y2K-Lessons-for-2038
References
- y2038 project wiki — evalEmpire/y2038 on GitHub — goals, Perl/Ruby/Python targets, "in Perl since 5.12.0", Perl Foundation funding.
- 2008Q2 Grant Proposal — Make localtime() and gmtime() Work Past 2038 — The Perl Foundation — grant for 64-bit localtime/gmtime on 32-bit machines.
- perl5120delta — perldoc.perl.org — "Y2038 compliance" section.
- Time::y2038 — MetaCPAN — CPAN module from the y2038 project; floating-point precision caveat.
- Time::Local — perldoc.perl.org — 32-bit range Dec 1901–Jan 2038 on older Perls; rolling century;
*_modernfunctions. - Bug 1057047 — perl Time::Local does not work with dates after 2038 — Red Hat Bugzilla — RHEL 5 Perl 5.8.8; fix for 64-bit only; RHBA-2014-1198.
- Ruby 1.9.2 Released — Linux Pro Magazine, 2010-08-19 — Time reimplemented, 2038 problem removed.
- class Time — Ruby documentation (master) — 63-bit/Bignum/Rational representation; no boundary limits; extrapolation beyond 32-bit
time_t. - Memory usage — Erlang System Documentation — small vs large integer sizes.
- Time and Time Correction in Erlang — Erlang/OTP documentation —
erlang:system_time()andos:system_time(). - Y2K38 and z/OS — David Stephens, LongEx Mainframe Quarterly, February 2026 — COBOL/PL/I don't store Unix time internally; z/OS 2.3/2.4 support.
- TIME — The GNU Fortran Compiler — returns
INTEGER(4); not fully portable; seeTIME8. - Date and Time Functions — MySQL 8.0 Reference Manual —
UNIX_TIMESTAMP()/FROM_UNIXTIME()ranges by version and platform. - DATEADD (Transact-SQL) — Microsoft Learn — int vs bigint number; Msg 8115.
- Avoiding the year 2038 problem — Gnulib manual, GNU —
year2038module enables_TIME_BITS=64on glibc ≥ 2.34. - Time Functions — The GNU Awk User's Guide —
systime(),mktime(),strftime()(page rate-limited during research; behaviour verified locally). - Lua 5.4 Reference Manual — lua.org —
os.timemeaning system-dependent; 64-bit integers by default;LUA_32BITS.