~ / topics / Languages
$man lang-perl-ruby-and-others

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

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:

Shell date, awk and Lua

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

Detection & mitigation

  1. 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)}'.
  2. Grep for pack(['"][lLN] (Perl/Ruby), :32 binary matches (Erlang), PIC S9(9) COMP (COBOL), time() (Fortran), INT epoch columns and DATEADD(s (SQL).
  3. Upgrade Perl/Ruby on 32-bit legacy hosts. Rebuild Lua without LUA_32BITS where memory allows.
  4. See Testing-and-Detection and Remediation-Guide.

See also

References

  1. y2038 project wiki — evalEmpire/y2038 on GitHub — goals, Perl/Ruby/Python targets, "in Perl since 5.12.0", Perl Foundation funding.
  2. 2008Q2 Grant Proposal — Make localtime() and gmtime() Work Past 2038 — The Perl Foundation — grant for 64-bit localtime/gmtime on 32-bit machines.
  3. perl5120delta — perldoc.perl.org — "Y2038 compliance" section.
  4. Time::y2038 — MetaCPAN — CPAN module from the y2038 project; floating-point precision caveat.
  5. Time::Local — perldoc.perl.org — 32-bit range Dec 1901–Jan 2038 on older Perls; rolling century; *_modern functions.
  6. 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.
  7. Ruby 1.9.2 Released — Linux Pro Magazine, 2010-08-19 — Time reimplemented, 2038 problem removed.
  8. class Time — Ruby documentation (master) — 63-bit/Bignum/Rational representation; no boundary limits; extrapolation beyond 32-bit time_t.
  9. Memory usage — Erlang System Documentation — small vs large integer sizes.
  10. Time and Time Correction in Erlang — Erlang/OTP documentation — erlang:system_time() and os:system_time().
  11. 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.
  12. TIME — The GNU Fortran Compiler — returns INTEGER(4); not fully portable; see TIME8.
  13. Date and Time Functions — MySQL 8.0 Reference Manual — UNIX_TIMESTAMP()/FROM_UNIXTIME() ranges by version and platform.
  14. DATEADD (Transact-SQL) — Microsoft Learn — int vs bigint number; Msg 8115.
  15. Avoiding the year 2038 problem — Gnulib manual, GNU — year2038 module enables _TIME_BITS=64 on glibc ≥ 2.34.
  16. Time Functions — The GNU Awk User's Guide — systime(), mktime(), strftime() (page rate-limited during research; behaviour verified locally).
  17. Lua 5.4 Reference Manual — lua.org — os.time meaning system-dependent; 64-bit integers by default; LUA_32BITS.