~ / topics / Languages
$man programming-languages

Programming Languages

In brief: Almost every modern programming language already handles dates after 2038 correctly in its standard date and time library. Programs still fail when a programmer stores a timestamp in a 32-bit integer, when data passes through a 32-bit file format, database column or network field, or when a language runs on top of a 32-bit operating system or C library. This page compares the major languages and links to a detailed page for each.

Risk level Medium overall. Language runtimes are mostly safe, but application code and 32-bit builds are not
Status Core time APIs are 64-bit (or wider) in all mainstream languages as of 2026-10-21. 32-bit builds of C-based runtimes and some systems interfaces remain exposed
Last reviewed 2026-10-21

Overview

The Year 2038 problem lives in data types, not languages. A language is exposed when one of these holds:

  1. Its default time type is 32-bit. That's rare today. C's time_t on 32-bit glibc is the main remaining case.
  2. It delegates to the C library. Python's time, PHP's mktime, Perl before 5.12, gawk, date and the JVM's native layer all inherit the platform time_t.
  3. Programmers narrow the value. (int), int32(), | 0, pack('i') and Int32Array silently or loudly truncate safe 64-bit values to 32 bits.
  4. The data leaves the program. Database TIMESTAMP/INT columns, binary protocols and file formats have their own widths. See Databases and File-Formats-and-Protocols.

The third and fourth causes affect every language, including those with unlimited integers.

Summary matrix

Language / runtime Default time representation Range / limit 2038 exposure Common pitfalls Details
C / C++ time_t (seconds); std::chrono::system_clock (usually int64 ns) 64-bit on LP64, MSVC and musl ≥ 1.2. 32-bit by default on 32-bit glibc (opt-in _TIME_BITS=64 since 2.34); chrono ≈ ±292 years High on 32-bit glibc and embedded. Low on 64-bit (int)t, %ld format, library ABIs exposing time_t, raw syscalls Lang-C-and-CPP
Java / Kotlin / Scala (JVM) long ms (currentTimeMillis); Instant = long s + int ns ±292 million years (ms); Instant ±1 billion years Low (platform). Medium (apps) (int)(ms/1000), JNI jint, 32-bit JVM native layer (JDK-8279444) Lang-Java-and-JVM
.NET (C#, F#, VB) DateTime Int64 ticks (100 ns since year 1) to 9999-12-31 Low (runtime). Medium (interop) (int)ToUnixTimeSeconds(), P/Invoke int for time_t, SQL Server DATEADD(int), Arm32 runtime before .NET 9 Lang-DotNet
Python time.time() float; time_ns() int; datetime year 1–9999 Unlimited ints. datetime to 9999 Low on 64-bit. Medium on 32-bit (C time_t) OverflowError from fromtimestamp on 32-bit, struct.pack('i'), C extensions Lang-Python
PHP int = C long/zend_long 64-bit builds: effectively unlimited. 32-bit builds: 1901–2038 High on 32-bit (and Windows PHP < 7) mktime/strtotime returning false, getTimestamp() ValueError, pack('l'), MySQL TIMESTAMP Lang-PHP
JavaScript / TypeScript Date = double ms ±8.64e15 ms (year 275760) Low (language). Medium (code) x \| 0, ~~x, Int32Array, readInt32LE, native build on 32-bit Arm Lang-JavaScript-and-TypeScript
Go time.Time; Unix() int64 billions of years Low. Medium on GOARCH=arm/386/mips via syscall int32(t.Unix()), syscall.Timespec{Sec int32} (issue #60248) Lang-Go-Rust-Swift
Rust SystemTime (platform struct); Duration u64 s Platform time_t. 64-bit on 64-bit targets Low. Medium on 32-bit glibc/musl targets as i32, libc crate 32-bit time_t (64-bit opt-in since 0.2.179) Lang-Go-Rust-Swift
Swift / Objective-C Date/NSDate = Double seconds since 2001-01-01 sub-ms precision over 10,000 years Low Int32(date.timeIntervalSince1970) traps; ObjC (int) casts Lang-Go-Rust-Swift
Perl IV/NV; core time functions 64-bit since 5.12 Unlimited for practical purposes Low (≥ 5.12). High for old Perls on 32-bit Time::Local on old Perls, 2-digit years, pack('l') Lang-Perl-Ruby-and-Others
Ruby Time = int63/Bignum/Rational (since 1.9.2) no boundary Low pack('l'); local time extrapolated on 32-bit time_t Lang-Perl-Ruby-and-Others
Erlang / Elixir arbitrary-precision integers unlimited Low <<Ts:32>> binary matches Lang-Perl-Ruby-and-Others
COBOL YYYYMMDD display/packed fields, day counts n/a Low internally. Medium at interfaces PIC S9(9) COMP epoch fields from Unix systems Lang-Perl-Ruby-and-Others
Fortran (GNU) TIME() → INTEGER(4); TIME8(); DATE_AND_TIME TIME() to 2038 Medium (legacy code) TIME() going negative Lang-Perl-Ruby-and-Others
SQL engine-specific types MySQL TIMESTAMP to 2038 High for TIMESTAMP/INT epoch columns UNIX_TIMESTAMP() returns 0 out of range; T-SQL DATEADD(second, int) Lang-Perl-Ruby-and-Others, Databases
Shell date, awk C time_t platform Medium on 32-bit builds date +%s math in scripts on old 32-bit hosts Lang-Perl-Ruby-and-Others
Lua os.time() number (64-bit integer by default since 5.3) platform-dependent Medium on LUA_32BITS embedded builds microcontroller firmware Lang-Perl-Ruby-and-Others

Sources for each row are on the linked pages. Key ones: glibc 2.34 [1], musl time64 [2], Java Instant [3], .NET DateTime.Ticks [4], Python time [5], PHP strtotime [6], MDN Date [7], Go time [8], Go issue #60248 [9], Rust libc #3248 [10], Apple TimeInterval [11], perl5120delta [12], Ruby Time [13].

Technical details: the universal bug and the universal fix

The same bug shows up in every language:

Language Bug (wraps or fails after 2038-01-19 03:14:07 UTC) Fix
C int t = time(NULL); time_t t = time(NULL); / int64_t
Java int t = (int)(System.currentTimeMillis()/1000); long t = Instant.now().getEpochSecond();
C# int t = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); long t = …ToUnixTimeSeconds();
Python struct.pack('<i', int(time.time())) struct.pack('<q', …)
PHP (32-bit) mktime(0,0,0,1,1,2040) → false new DateTimeImmutable('2040-01-01')
JavaScript Date.now()/1000 \| 0 Math.floor(Date.now()/1000)
Go int32(time.Now().Unix()) time.Now().Unix() (int64)
Rust secs as i32 i32::try_from(secs)? or keep u64/i64
Swift Int32(d.timeIntervalSince1970) (traps) Int64(…)

Rules that apply everywhere:

  1. Keep time in the language's native date/time type (Instant, DateTimeOffset, datetime, DateTimeImmutable, time.Time, Date) for as long as possible.
  2. At boundaries, use 64-bit signed seconds (or ms/µs/ns) or ISO 8601 strings.
  3. Where a 32-bit field truly can't change, use a checked conversion that fails loudly: Math.toIntExact, checked(...), try_from, Int32(exactly:).
  4. On 32-bit platforms, verify that the runtime itself was built with 64-bit time_t (glibc _TIME_BITS=64, musl ≥ 1.2).

Risk & compliance notes

Detection & mitigation

See also

References

  1. GNU C Library 2.34 released — LWN.net, 2021-08-01 — 64-bit time_t opt-in on 32-bit glibc via _TIME_BITS=64.
  2. time64 — musl libc — musl 1.2.0 makes time_t 64-bit on all archs.
  3. java.time.Instant — Java SE 21 API, Oracle — long seconds + int nanos; ±1 billion years.
  4. DateTime.Ticks Property — Microsoft Learn — Int64 100 ns ticks since 0001-01-01.
  5. time — Python 3 documentation — C-library cut-off "for 32-bit systems, it is typically in 2038".
  6. strtotime — PHP Manual — 32-bit range 1901–2038; 64-bit "effectively infinite".
  7. Date — JavaScript, MDN Web Docs — ±8.64e15 ms range.
  8. Package time — pkg.go.dev — Unix() int64.
  9. golang/go issue #60248 — GitHub — 32-bit time_t types in syscall on linux/arm, 386, mips.
  10. Tracking issue for libc 1.0 — rust-lang/libc #3248 — single time_t definition; 64-bit time plans.
  11. TimeInterval — Apple Developer Documentation — Double; sub-millisecond precision over 10,000 years.
  12. perl5120delta — perldoc.perl.org — core time functions Y2038 compliant.
  13. class Time — Ruby documentation — no boundary or resolution limitations.